Runtime系统是一个由一系列C语言函数和数据结构组成的动态共享库,即通过面向过程语言C实现Objective-C语言的面向对象特性。

1 、概述

Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行,这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C汇编写的,这个库使得C语言有了面向对象的能力

Objective-C runtime目前有两个版本:

1) Modern Runtime :在Objective-C 2.0 引进,覆盖了64位的Mac OS X Apps,还有 iOS Apps;

2) Legacy Runtime 是早期用来给32位 Mac OS X Apps 用的,也就是可以不用管就是了。

这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。

2、类与对象

2.1 面向对象模型

在面向对象编程模型中,主要围绕两种结构进行设计和编程:类与对象。

2.1.1 对象

对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。

对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中:

           1) 数据:一个对象用数据值来描述它的状态。

           2) 操作:用于改变对象的状态,对象及其操作就是对象的行为。

2.1.2 类

具有相同特性(数据元素)和行为(功能)的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象,类实际上就是一种数据类型。

2.2 数据结构

由于Objective-C底层是通过C语言实现,但C语言又不存在类和对象。为了描述面向对象的特性,所以Objective-C使用C语言的结构体来表示对象,即Objective-C语言中的类和对象本身是由结构体表示。

2.2.1 类结构

Objective-C的类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。可以在[3]项目Public Headers/runtime.h目录下发现有如下定义:

定义 21:objc_class结构体

typedef struct objc_class *Class;

struct objc_class

{

          Class isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

Class super_class //父类

const char *name //类名

long version //类的版本信息,默认为0

long info //类信息,供运行期使用的一些位标识

long instance_size //该类的实例变量大小

struct objc_ivar_list *ivars //该类的成员变量链表

struct objc_method_list **methodLists //方法定义的链表

struct objc_cache *cache //方法缓存

struct objc_protocol_list *protocols //协议链表

#endif

} OBJC2_UNAVAILABLE;

在这个定义中,下面几个字段是我们感兴趣的

1) isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),在后面会介绍它。

2) super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

2.2.2 对象结构

在Objective-C中的对象由objc_object结构体表示,它的定义如下(objc/objc.h):

定义 22:objc_object结构体

struct objc_object

{

          Class isa OBJC_ISA_AVAILABILITY;

};

typedef struct objc_object *id;

可以看到,这个结构体只有一个成员,即指向其类的isa指针。这样,当向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后即运行这个方法。

2.3 Objective-C设计模型

C++模型中有两种结构:静态成员和非静态成员。静态成员属于整个类所有的对象包括类,任何对象都可以操作;而非静态成员只属于实例化后的某个对象,是对象所独有的,若未实例化则无法访问。

类似C++模型,Objective-C模型也有两种操作实例的方式:实例方法和类方法。若要操作实例方法需要将类进行实例化为对象,然后通过对象来操作方法;若要操作类方法,则不需要实现化,直接通过类即可操作类方法。

由于Objective-C模型存在实例操作方法和类操作方法,所以runtime系统需要某种方式来存储这两种方法:

       1) 对象实例:该实例只是一种连续的二进制数据流(即void*类型),用于存放对象中的数据(即Objective-C成员变量),并且对象实例的二进制数据流中第一个数据类型永远为objc_class指针(命名为isa),之后才存放其它数据成员,之后其它数据成员占多大的字节由类实例(即objc_class对象)类定义。

      2)类实例:该实例是objc_class结构体的实例化对象,是对象实例的一种"导航",其存放对象实例的所有信息,包括二进制数据流的分配和对象实例提供的操作方法等各种。

    3)元类实例:该实例也是objc_class结构体的实例化对象,不同的是该实例是类实例的"导航",其存放类实例的所有信息,当要操作类方法时,就通过元类实例来操作类实例。

图 21 Objective-C实例关系图

2.3.1 对象实例

2.3.1.1 内存空间布局

对象实例其实是一种连续的二进制数据流(即void*类型),其存放两种内容:objc_class指针和Objective-C对象的所有数据。而对象数据如何在二进制数据中组织,及对象的操作方法则存放在类实例中,并且创建的对象实例的大小由类实例中的instance_size值决定。

图 22 对象实例内存空间布局

如创建创建一个MyClass对象,其内存空间布局如表 21所示:

@interface MyClass

{

int age;

float height;

}

@end

 

表 21 MyClass对象实例的内存布局

序号

成员

字节占用大小

0

objc_class *isa

8

1

int age

4

2

float height

4

2.3.1.2 创建对象实例

当创建Objective-C对象时,runtime通过malloc创建一块堆空间,然后再强制转换为objc_object*类型,最后初始化其isa。可在runtime.h文件中发现其创建的过程:class_createInstance() --> _class_createInstance() --> _class_createInstanceFromZone()

 1 id  _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
 2 {    ...
 3     size = cls->alignedInstanceSize() + extraBytes;   //获取对象实例的空间大小
 4     bytes = calloc(, size);     //创建内存空间
 5     ...
 6     return objc_constructInstance(cls, bytes);
 7 }
 8 id  objc_constructInstance(Class cls, void *bytes) 
 9 {
     ...
     id obj = (id)bytes;  //将void*类型强制转换为objc_object*类型
     obj->initIsa(cls);   //初始化objc_object结构体中的isa成员
     ...        
     return obj;
 }

2.3.1.3 访问成员变量

访问成员变量首先通过对象实例中的isa指针,获取objc_class对象中指定变量的信息;然后再访问对象实例中的某块内存。

     1) 获取变量信息

变量的信息都存储在类实例(即objc_class对象)中,所以根据对象实例(即objc_object对象)中的isa指针索引到类实例,然后遍历类实例中定义了变量。可在runtime.h文件中的 class_getInstanceVariable方法查看其过程:

 1 Ivar class_getInstanceVariable(Class cls, const char *name)
 2 {
 3     if (!cls  ||  !name) return nil;
 4     return _class_getVariable(cls, name, nil);
 5 }
 6 Ivar _class_getVariable(Class cls, const char *name, Class *memberOf)
 7 {
 8     for (; cls != Nil; cls = cls->superclass) {   //遍历继承结构所有的变量
 9         int i;
         for (i = ; i < cls->ivars->ivar_count; i++) {
             old_ivar *ivar = &cls->ivars->ivar_list[i];
             if (ivar->ivar_name  &&   == strcmp(name, ivar->ivar_name)) {  //比较是否与指定的变量相同
                 if (memberOf) *memberOf = cls;
                 return (Ivar)ivar;
             }
         }
     }
     return nil;
 }

     2) 获取实例内容

根据objc_class对象中定义的变量信息,可计算出变量的偏移量,从而获取变量的地址,最后可获取或设置objc_object对象中的变量值。如runtime.h文件中的object_getIvar和object_setIvar方法:

 1 id object_getIvar(id obj, Ivar ivar)
 2 {        ...
 3         ptrdiff_t ivar_offset = ivar_getOffset(ivar);  //获取变量的偏移量
 4         id *idx = (id *)((char *)obj + ivar_offset);  //计算变量的起始地址,并强制转换为id*类型。
 5         ...
 6         return *idx;
 7  }
 8 void object_setIvar(id obj, Ivar ivar, id value)
 9 {        ...
         ptrdiff_t ivar_offset = ivar_getOffset(ivar);  //获取变量的偏移量
         id *location = (id *)((char *)obj + ivar_offset);  //计算变量的起始地址,并强制转换为id*类型。
         ...
         *location = value;  //设置为指定值
         ...
 }

2.3.2 类实例

2.3.2.1 内存空间布局

类实例是objc_class结构体的实例化对象,若创建的类是基类,则创建实例大小为sizeof(objc_class);若创建的类不是基类,则其实例创建的内存空间大小是由isa指针的元类(即objc_class对象)决定。

其中每个Objective-C类只实例化一个类实例,所有该类型的对象实例中的isa指针都指向该类实例。一般由编译器自动实例化。如下所示,不同的对象拥有同一个类实例:

 1 -(void)testMetaClass
 2 {
 3     MyClass *myClass1 = [[MyClass alloc] init];
 4     Class isa1 = [myClass1 class];
 5     NSLog(@"object1 is %p.", myClass1);
 6     NSLog(@"object1 isa pointer: %p", isa1);
 7     
 8     MyClass *myClass2 = [[MyClass alloc] init];
 9     Class isa2 = [myClass2 class];
     NSLog(@"object2 is %p.", myClass2);
     NSLog(@"object2 isa pointer: %p", isa2);
 }
 -- ::02.808 runtimeProject[:] object1 is 0x7fda02c1d100.
 -- ::02.808 runtimeProject[:] object1 isa pointer: 0x1069a13e0
 -- ::02.808 runtimeProject[:] object2 is 0x7fda02d25d00.
 -- ::02.809 runtimeProject[:] object2 isa pointer: 0x1069a13e0

2.3.2.2 创建类实例

创建类实例,同样是申请其内存空间占用的大小;然后强制转换为objc_class*;最后对类实例的成员进行初始化。可在runtime.h文件中查看objc_allocateClassPair方法创建一个没有任何成员的类实例的实现:

 1 Class objc_allocateClassPair(Class supercls, const char *name,  size_t extraBytes)
 2 {
 3     Class cls, meta;
 4     // Allocate new classes. 
 5     if (supercls) {  //有父类
 6         cls = (Class) calloc(, supercls->ISA()->alignedInstanceSize() + extraBytes);
 7         meta = (Class) calloc(, supercls->ISA()->ISA()->alignedInstanceSize() + extraBytes);
 8     } else {  //无父类
 9         cls = (Class) calloc(, sizeof(objc_class) + extraBytes);
         meta = (Class) calloc(, sizeof(objc_class) + extraBytes);
     }
 
     objc_initializeClassPair(supercls, name, cls, meta);
     return cls;
 }
 Class objc_initializeClassPair(Class supercls, const char *name, Class cls, Class meta)
 {
     // Connect to superclasses and metaclasses
     cls->initIsa(meta);
     set_superclass(cls, supercls, YES);
 
     // Set basic info
     cls->name = strdup(name);
     meta->name = strdup(name);
     cls->version = ;
     meta->version = ;
     cls->info = CLS_CLASS | CLS_CONSTRUCTING | CLS_EXT | CLS_LEAF;
     meta->info = CLS_META | CLS_CONSTRUCTING | CLS_EXT | CLS_LEAF;
 
     // Set instance size based on superclass.
     if (supercls) {
         cls->instance_size = supercls->instance_size;
         meta->instance_size = supercls->ISA()->instance_size;
     } else {
         cls->instance_size = sizeof(Class);  // just an isa
         meta->instance_size = sizeof(objc_class);
     }
     
     // No ivars. No methods. Empty cache. No protocols. No layout. Empty ext.
     cls->ivars = nil;
     cls->methodLists = nil;
     cls->cache = (Cache)&_objc_empty_cache;
     cls->protocols = nil;
     cls->ivar_layout = &UnsetLayout;
     cls->ext = nil;
     allocateExt(cls);
     cls->ext->weak_ivar_layout = &UnsetLayout;
 
     meta->ivars = nil;
     meta->methodLists = nil;
     meta->cache = (Cache)&_objc_empty_cache;
     meta->protocols = nil;
     meta->ext = nil;
     
     return cls;
 }

2.3.2.3 添加成员变量

objc_allocateClassPair方法只创建了一个没有任何成员的类实例,若要添加变量可以调用class_addIvar方法。由于类实例是对象实例操作的导航对象,若往类实例中添加了新变量,则需要修改objc_class对象中的instance_size成员值。可查看runtime.h文件中的class_addIvar方法实现:

 1 BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *type)
 2 {
 3         old_ivar_list *old = cls->ivars;
 4         size_t oldSize;
 5         int newCount;
 6         old_ivar *ivar;
 7         ...
 8         // 创建新的地址空间,并复制原来变量链表中的内容
 9         cls->ivars = (old_ivar_list *) calloc(oldSize+sizeof(old_ivar), );
         if (old) memcpy(cls->ivars, old, oldSize);
         if (old  &&  malloc_size(old)) free(old);
         cls->ivars->ivar_count = newCount;
         ivar = &cls->ivars->ivar_list[newCount-];
 
         // 设置新添加变量的内容
         ivar->ivar_name = strdup(name);
         ivar->ivar_type = strdup(type);
         ivar->ivar_offset = (int)cls->instance_size;
 
         cls->instance_size += (long)size;  // 加长对象实例的大小
 
     return result;
 }

2.3.3 元类实例

2.3.3.1 内存空间布局

元类实例也是objc_class结构体的实例化对象,并且其创建过程也可在objc_allocateClassPair方法中进行,同时同一个类的所有对象拥有同一个元类实例。不同的是该实例是类实例的"导航",其存放类实例的所有信息,当要操作类方法时,就通过元类实例来操作类实例。

2.3.3.2 三实例的关系

对于同一个类中的对象实例、类实例和元类实例之间存在紧密的联系,其中对象实例存在多个,而类实例和元类实例是只存在一个。由定义 21和定义 22可知,objc_object结构体存在isa指针,而objc_class结构体存在isa和superclass指针,三实例就是通过这些指针进行联系的,如图 22所示是三者的关系图。

图 23 三实例之间的关系

注意:

1) root class为基类,一般NSObject为基类,若创建的类不基础任何类,则所创建的类为基类。

2) 所有元类的isa指针都指向root class的元类实例,包括root class meta本身。

3) class meta的superclass指针指向父类的meta,但root class meta的superclass指针则指向root class。

4) root class的superclass指针为空。

2.4 简单示例

2.4.1 动态创建类和对象

本例在运行时,动态创建一个类,并添加一个方法;接着创建给类的一个实例对象。在类的方法中输出其对象实例、类实例、元类实例、基元类实例等地址,从而验证图 22所示关系图的正确性。

 1 void ReportFunction(id self, SEL _cmd)
 2 {
 3     NSLog(@"This object is %p.", self);
 4     NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
 5     
 6     Class currentClass = [self class];
 7     for (int i = ; i < ; i++)
 8     {
 9         NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
         currentClass = object_getClass(currentClass);
     }
     
     NSLog(@"NSObject's class is %p", [NSObject class]);
     NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
 }
 -(void) report
 {
     Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", );
     class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");//"v@:"用于描述所添加方法的参数类型。v表示方法的返回参数类型,@表示第一个参数的类型,:表示第二个参数的类型。更多类型可参考Objective-C Runtime Programming Guide > Type Encodings.
     objc_registerClassPair(newClass);
     
     id instanceOfNewClass = [[newClass alloc] initWithDomain:@"someDomain" code: userInfo:nil];
     [instanceOfNewClass performSelector:@selector(report)];
     [instanceOfNewClass release];
 }
 -(void) main
 {
     [self report];
     return;
 }
 -- ::41.173 runtimeProject[:] This object is 0x7fd5c8c02d30.
 -- ::41.174 runtimeProject[:] Class is RuntimeErrorSubclass, and super is NSError.
 -- ::41.174 runtimeProject[:] Following the isa pointer  times gives 0x7fd5c8c03180
 -- ::41.174 runtimeProject[:] Following the isa pointer  times gives 0x7fd5c8c048c0
 -- ::41.174 runtimeProject[:] Following the isa pointer  times gives 0x103ada198
 -- ::41.175 runtimeProject[:] Following the isa pointer  times gives 0x103ada198
 -- ::41.175 runtimeProject[:] NSObject's class is 0x103ada170
 -- ::41.175 runtimeProject[:] NSObject's meta class is 0x103ada198

2.4.2 类与对象的操作函数

本例采用静态方式创建一个类,然后采用动态方式输出该类的信息。

  1 @interface MyClass : NSObject<NSCopying, NSCoding>
  2 @property (nonatomic, strong) NSArray *array;
  3 @property (nonatomic, copy) NSString *string;
  4 
  5 - (void)method1;
  6 - (void)method2;
  7 + (void)classMethod1;
  8 @end
  9 -(void) main
 10 {
 11     @autoreleasepool {
 12         MyClass *myClass = [[MyClass alloc] init];
 13         unsigned int outCount = ;
 14         Class cls = myClass.class;
 15         // 类名
 16         NSLog(@"class name: %s", class_getName(cls));
 17         NSLog(@"==========================================================");
 18         // 父类
 19         NSLog(@"super class name: %s", class_getName(class_getSuperclass(cls)));
 20         NSLog(@"==========================================================");
 21         
 22         // 是否是元类
 23         NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ? @"" : @"not"));
 24         NSLog(@"==========================================================");
 25         
 26         Class meta_class = objc_getMetaClass(class_getName(cls));
 27         NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(meta_class));
 28         NSLog(@"==========================================================");
 29         
 30         // 变量实例大小
 31         NSLog(@"instance size: %zu", class_getInstanceSize(cls));
 32         NSLog(@"==========================================================");
 33         
 34         // 成员变量
 35         Ivar *ivars = class_copyIvarList(cls, &outCount);
 36         for (int i = ; i < outCount; i++) {
 37             Ivar ivar = ivars[i];
 38             NSLog(@"instance variable's name: %s at index: %d", ivar_getName(ivar), i);
 39         }
 40         free(ivars);
 41         
 42         Ivar string = class_getInstanceVariable(cls, "_string");
 43         if (string != NULL) {
 44             NSLog(@"instace variable %s", ivar_getName(string));
 45         }
 46         
 47         NSLog(@"==========================================================");
 48         // 属性操作
 49         objc_property_t * properties = class_copyPropertyList(cls, &outCount);
 50         for (int i = ; i < outCount; i++) {
 51             objc_property_t property = properties[i];
 52             NSLog(@"property's name: %s", property_getName(property));
 53         }
 54         
 55         free(properties);
 56 
 57         objc_property_t array = class_getProperty(cls, "array");
 58         if (array != NULL) {
 59             NSLog(@"property %s", property_getName(array));
 60         }
 61 
 62         NSLog(@"==========================================================");
 63         // 方法操作
 64         Method *methods = class_copyMethodList(cls, &outCount);
 65         for (int i = ; i < outCount; i++) {
 66             Method method = methods[i];
 67             NSLog(@"method's signature: %s", method_getName(method));
 68         }
 69 
 70         free(methods);
 71         Method method1 = class_getInstanceMethod(cls, @selector(method1));
 72         if (method1 != NULL) {
 73             NSLog(@"method %s", method_getName(method1));
 74         }
 75 
 76         Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
 77         if (classMethod != NULL) {
 78             NSLog(@"class method : %s", method_getName(classMethod));
 79         }
 80         
 81         NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"" : @" not");
 82         IMP imp = class_getMethodImplementation(cls, @selector(method1));
 83         imp();
 84         
 85         NSLog(@"==========================================================");
 86         // 协议
 87         Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
 88         Protocol * protocol;
 89         for (int i = ; i < outCount; i++) {
 90             protocol = protocols[i];
 91             NSLog(@"protocol name: %s", protocol_getName(protocol));
 92         }
 93         
 94         NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
 95         NSLog(@"==========================================================");
 96     }
 97 }
 98 -- ::02.403 runtimeProject[:] class name: MyClass
 99 -- ::02.404 runtimeProject[:] ==========================================================
 -- ::02.404 runtimeProject[:] super class name: NSObject
 -- ::02.404 runtimeProject[:] ==========================================================
 -- ::02.404 runtimeProject[:] MyClass is not a meta-class
 -- ::02.404 runtimeProject[:] ==========================================================
 -- ::02.404 runtimeProject[:] MyClass's meta-class is MyClass
 -- ::02.405 runtimeProject[:] ==========================================================
 -- ::02.405 runtimeProject[:] instance size: 
 -- ::02.405 runtimeProject[:] ==========================================================
 -- ::02.405 runtimeProject[:] instance variable's name: _array at index: 0
 -- ::02.405 runtimeProject[:] instance variable's name: _string at index: 1
 -- ::02.405 runtimeProject[:] instace variable _string
 -- ::02.405 runtimeProject[:] ==========================================================
 -- ::02.406 runtimeProject[:] property's name: array
 -- ::02.406 runtimeProject[:] property's name: string
 -- ::02.406 runtimeProject[:] property array
 -- ::02.406 runtimeProject[:] ==========================================================
 -- ::02.406 runtimeProject[:] method's signature: method1
 -- ::02.407 runtimeProject[:] method's signature: method2
 -- ::02.407 runtimeProject[:] method's signature: method3WithArg1:arg2:
 -- ::02.407 runtimeProject[:] method's signature: setArray:
 -- ::02.407 runtimeProject[:] method's signature: string
 -- ::02.407 runtimeProject[:] method's signature: setString:
 -- ::02.407 runtimeProject[:] method's signature: array
 -- ::02.408 runtimeProject[:] method method1
 -- ::02.408 runtimeProject[:] class method : classMethod1
 -- ::02.408 runtimeProject[:] MyClass is responsd to selector: method3WithArg1:arg2:
 -- ::02.408 runtimeProject[:] call method method1
 -- ::02.409 runtimeProject[:] ==========================================================
 -- ::02.409 runtimeProject[:] protocol name: NSCopying
 -- ::02.409 runtimeProject[:] protocol name: NSCoding
 -- ::02.409 runtimeProject[:] MyClass is responsed to protocol NSCoding
 -- ::02.410 runtimeProject[:] ==========================================================

3、成员变量与属性

3.1 数据结构

3.1.1 Ivar

Ivar是表示实例变量的类型,其实际是一个指向objc_ivar结构体的指针,其定义如下:

typedef struct objc_ivar *Ivar;

struct objc_ivar {

char *ivar_name OBJC2_UNAVAILABLE; // 变量名

char *ivar_type OBJC2_UNAVAILABLE; // 变量类型

int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字节

#ifdef __LP64__

int space OBJC2_UNAVAILABLE;

#endif

}

objc_ivar结构体中的ivar_type成员描述了变量的类型,而runtime系统定义了一套标识符来描述Objective-C语言中的每种类型。通过@encode编译器指令,可从Objective-C类型的编译为runtime标识符,即@encode返回这个类型的字符串编码。这些类型可以是诸如int、指针这样的基本类型,也可以是结构体、类等类型。事实上,任何可以作为sizeof()操作参数的类型都可以用于@encode()。所有编码类型可参考在[2]Type Encoding小节。

一个数组的类型编码位于方括号中;其中包含数组元素的个数及元素类型。如所示:

 float a[] = {1.0, 2.0, 3.0};
 NSLog(@"array encoding type: %s", @encode(typeof(a)));
 -- ::28.489 runtimeProject[:] array encoding type: [3f]

另外对于属性而言,还会有一些特殊的类型编码,以表明属性是只读、拷贝、retain等等,详情可以参考[2]Property Type String小节。

3.1.2 objc_property_t

objc_property_t是表示Objective-C声明的属性的类型,及定义了objc_property_attribute_t,为属性的特性(attribute)。如下所示:

typedef struct objc_property *objc_property_t;

typedef struct {

const char *name; /**< The name of the attribute */

const char *value; /**< The value of the attribute (usually empty) */

} objc_property_attribute_t;

4、方法与消息

4.1 数据结构

4.1.1 SEL

      1) 定义

SEL又叫选择器,用于标识一个方法。Objective-C在编译时,会依据每一个方法的名字生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。如下是SEL的定义:

定义 41 SEL结构体定义

typedef struct objc_selector *SEL;

SEL sel1 = @selector(method1);

NSLog(@"sel : %p", sel1);

2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72

注意:

        可以通过sel_registerName函数动态添加一个新的SEL,并且不能将C类型的字符串直接转换为SEL,需要通过@selector()宏定义进行转换。    

        2)SEL关系

SEL在runtime中是唯一的,即只要方法或函数的名字相同,那么生成的SEL标识值就一样。所以在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致了Objective-C语言中不存在函数多态,如在某个类中定义以下两个方法,将发生编译错误:

- (void)setWidth:(int)width;

- (void)setWidth:(double)width;

当然,不同的类可以拥有相同的selector,这个没有问题。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。

4.1.2 IMP

IMP实际上是一个函数指针,指向函数实现的首地址。即IMP是一个函数的通用实现结构体,表示一个函数的实现,其定义如下:

定义 42 IMP结构体定义

id (*IMP)(id, SEL, ...)

第一个参数:指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针);

第二个参数:方法选择器(selector);

后续参数:为方法的实际参数列表。

4.1.3 Method

函数由两部分组成:函数声明和函数实现。函数声明由SEL结构体表示,而函数实现由IMP结构体表示。在runtime中采用Method结构体来描述类的函数,其定义如下:

定义 43 Method结构体定义

typedef struct objc_method *Method;

struct objc_method {

SEL method_name OBJC2_UNAVAILABLE;

char *method_types OBJC2_UNAVAILABLE;

IMP method_imp OBJC2_UNAVAILABLE;

}

可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。

4.2 消息发送

4.2.1 发送流程

在Objective-C中,消息直到运行时才会绑定到方法的实现上。如当执行"[receiver message]"语句时,会被转换为"objc_msgSend(receiver, selector)"语句。而objc_msgSend函数在runtime的声明为:

id objc_msgSend(id self, SEL op, ...)

self参数:指向对象实例的地址;

op参数:为方法名的转换标识;

后续参数:方法的参数。

objc_msgSend函数通过receiver实例和selector标识符就能找到相应方法的实现(IMP),从而调用对象的方法。这个搜索对象方法实现的过程就是搜索selector标识符的过程,如图 41所示。

1) 首先,从当前receiver实例中,获取isa指针,并获取objc_class结构体对象。

2) 接着,从objc_class对象的cache methodLists查找是否有selector。

3) 若无,从objc_class对象的methodLists查找是否有selector。

4) 若无,从objc_class对象的super_class指针所指向的父类中,继续查找。

5) 以此类推,直至找到,则调用objc_method结构体的method_imp成员,即调用方法的实现。

6) 最后再找不到,就会进入动态方法解析和消息转发的机制。

图 41 Messaging Framework

4.3 消息转发

当一个对象能接收一个消息时,就会走正常的方法调用流程。但如果一个对象接收到无法处理的消息时,它会按序调用该对象的如下方法:

1) resolveInstanceMethod 或 resolveClassMethod

2) forwardingTargetForSelector

3) forwardInvocation

若上述方法中,仍无法处理消息,那么将抛出一个异常错误,如下所示:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyClass report]: unrecognized selector sent to instance 0x7ff3134a81c0'

图 42 消息的处理流程

4.3.1 动态方法解析

若对象接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法),从两个方法中查找是否存在所接收的消息,若返回YES表示存在,系统会再次向该对象发送消息。

可以在上述两方法中调用class_addMethod函数,向某个类动态添加新方法和实现,其声明为:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

cls参数:为添加到某类。

name参数:响应消息的名字,即name=@selector(methodName)。

imp参数:为方法的实现地址,即函数的地址。被添加方法至少要有两个参数,参数类型为id和SEL。

types参数:是一个字符串数组,描述所添加的方法的返回值类型、参数列表的数值类型,

注意:

当resolveInstanceMethod或resolveClassMethod方法返回YES后,系统会再次发送消息,若此时仍无法响应消息,将抛出异常错误。

如下是重载某类的resolveInstanceMethod方法,类实现动态添加方法,也可以参考2.4.1动态创建类和对象例子。

 1 void dynamicMethodIMP(id self, SEL _cmd) {    //被添加的方法
 2     // implementation ....
 3 }
 4 @implementation MyClass
 5 + (BOOL)resolveInstanceMethod:(SEL)aSEL
 6 {
 7     if (aSEL == @selector(resolveThisMethodDynamically)) {
 8           class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
 9           return YES;  //表示找到,让其重新调用。
     }
     return [super resolveInstanceMethod:aSEL];  //若找不到,则往父类中查找是否存在aSEL消息的响应方法
 }
 @end

4.3.2 备用接收者

如果上述resolveInstanceMethod或resolveClassMethod方法返回NO后,则Runtime会继续调以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

如果一个对象实现了这个方法,并返回一个非nil的结果,则这个返回的对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

4.3.3 完整消息转发

如果上述forwardingTargetForSelector返回nil,则Runtime会继续调以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

forwardInvocation:方法的实现有两个任务:

1) 定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。

2) 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。

还有一个很重要的问题,我们必须重写以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。

备注:

个人感觉,没必要到第三步来处理未知的消息,这个太麻烦了,能处理的操作直接在上述两步完成即可。

5、参考文献

[1].Objective-C Runtime Reference

[2].Objective-C Runtime Programming Guide

[3].Objective-C 源码

[4].《Objective-C Runtime 运行时》序列文章

[5].《Objective-C Runtime》博客

[6].详解Objective-C的meta-class

[7].刨根问底Objective-C Runtime

Objective-C:runtime的更多相关文章

  1. 刨根问底Objective-C Runtime

    http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective%5Bnil%5Dc-runtime-(2)%5Bnil%5D-object-and- ...

  2. 刨根问底Objective-C Runtime(4)- 成员变量与属性

    http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-lian ...

  3. ios开发runtime学习二:runtime交换方法

    #import "ViewController.h" /* Runtime(交换方法):主要想修改系统的方法实现 需求: 比如说有一个项目,已经开发了2年,忽然项目负责人添加一个功 ...

  4. iOS开发runtime学习:一:runtime简介与runtime的消息机制

    一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/messag ...

  5. iOS开发——高级篇——Objective-C特性:Runtime

    Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime系统来动态创建类和对象,进行消息发送和转发.下面通过分析Apple开源的 ...

  6. Objective-O Runtime 运行时初体验

    Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一 ...

  7. iOS:runtime最全的知识总结

    runtime 完整总结 好东西,应该拿出来与大家分享... 南峰子博客地址:http://southpeak.github.io/blog/categories/ios/ 原文链接:http://w ...

  8. .NET:CLR via C#:Runtime Serialization

    Making a Type Serializable The SerializableAttribute custom attribute may be applied to reference ty ...

  9. Apache Flink 进阶(一):Runtime 核心机制剖析

    1. 综述 本文主要介绍 Flink Runtime 的作业执行的核心机制.首先介绍 Flink Runtime 的整体架构以及 Job 的基本执行流程,然后介绍在这个过程,Flink 是怎么进行资源 ...

随机推荐

  1. Oracle Form属性、内置子程序、触发器、系统变量简要

    一.属性 1.1 通用属性 名称(Name) 子类信息(Subclass Information) 备注(Comments) 标题(Title) 方向(Direction) 字体名称(Font Nam ...

  2. centos6.5安装vbox

    cd /etc/yum.repos.d wget http://download.virtualbox.org/virtualbox/rpm/rhel/virtualbox.repo 下载跟CENTO ...

  3. C#的同步和异步调用方法

    同步和异步大家都明白什么意思,在这里不多介绍了. namespace ConsoleTest { class Program { static void Main(string[] args) { C ...

  4. WIKIOI 3243 区间翻转

    3243 区间翻转 题目描述 Description 给出N个数,要求做M次区间翻转(如1 2 3 4变成4 3 2 1),求出最后的序列 输入描述 Input Description 第一行一个数N ...

  5. 【CSS3】

    Web前端实验室http://demo.doyoe.com/ ::before ::afterCSS3已经将伪元素的前缀更改为双冒号,而伪类则保持为单冒号 backface-visibility ht ...

  6. 一个不错的php图片处理类EasyPhpThumbnail Class

    EasyPhpThumbnail Class   EasyPhpThumbnail Class用于处理图片操作和生成缩略图.支持GIF.JPG和PNG三种格式. 提供的功能包括:Resize.剪切.旋 ...

  7. 面试准备--struts2.x

    对象解析: 1.HttpServletRequest对象是tomcat对用户请求信息的封装,该对象提供了多个方法可以获取用户的请求信息. 2.ActionContextCleanUP是一个可选过滤器, ...

  8. nginx的优缺点

    1.nginx相对于apache优点: 轻量级同样起web 服务比apache占用更少内存及资源 抗并发nginx 处理请求异步非阻塞而apache 则阻塞型高并发下nginx 能保持低资源低消耗高性 ...

  9. [转]ASP.NET MVC 入门3、Routing

    在一个route中,通过在大括号中放一个占位符来定义( { and } ).当解析URL的时候,符号"/"和"."被作为一个定义符来解析,而定义符之间的值则匹配 ...

  10. Appium 小白从零安装 ,Appium连接真机测试。

    以下是我个人在初次安装使用Appium时的过程,过程中遇到了一些问题,在这里也一一给出解决办法. Appium安装过程 先安装了 Node.js.在node的官网上下载的exe安装文件. 在node的 ...