Objective-C:runtime

Objective-C:runtime

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系统需要某种方式来存储这两种方法:

  • 对象实例:该实例只是一种连续的二进制数据流(即void*类型),用于存放对象中的数据(即Objective-C成员变量),并且对象实例的二进制数据流中第一个数据类型永远为objc_class指针(命名为isa),之后才存放其它数据成员,之后其它数据成员占多大的字节由类实例(即objc_class对象)类定义。
  • 类实例:该实例是objc_class结构体的实例化对象,是对象实例的一种"导航",其存放对象实例的所有信息,包括二进制数据流的分配和对象实例提供的操作方法等各种。
  • 元类实例:该实例也是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(1, size);     //创建内存空间
 5     ...
 6     return objc_constructInstance(cls, bytes);
 7 }
 8 id  objc_constructInstance(Class cls, void *bytes) 
 9 {
10     ...
11     id obj = (id)bytes;  //将void*类型强制转换为objc_object*类型
12     obj->initIsa(cls);   //初始化objc_object结构体中的isa成员
13     ...        
14     return obj;
15 }

2.3.1.3 访问成员变量

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

  • 获取变量信息

变量的信息都存储在类实例(即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;
10         for (i = 0; i < cls->ivars->ivar_count; i++) {
11             old_ivar *ivar = &cls->ivars->ivar_list[i];
12             if (ivar->ivar_name  &&  0 == strcmp(name, ivar->ivar_name)) {  //比较是否与指定的变量相同
13                 if (memberOf) *memberOf = cls;
14                 return (Ivar)ivar;
15             }
16         }
17     }
18     return nil;
19 }

  • 获取实例内容

根据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 {        ...
10         ptrdiff_t ivar_offset = ivar_getOffset(ivar);  //获取变量的偏移量
11         id *location = (id *)((char *)obj + ivar_offset);  //计算变量的起始地址,并强制转换为id*类型。
12         ...
13         *location = value;  //设置为指定值
14         ...
15 }

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];
10     NSLog(@"object2 is %p.", myClass2);
11     NSLog(@"object2 isa pointer: %p", isa2);
12 }
13 2016-02-18 20:30:02.808 runtimeProject[1052:78156] object1 is 0x7fda02c1d100.
14 2016-02-18 20:30:02.808 runtimeProject[1052:78156] object1 isa pointer: 0x1069a13e0
15 2016-02-18 20:30:02.808 runtimeProject[1052:78156] object2 is 0x7fda02d25d00.
16 2016-02-18 20:30:02.809 runtimeProject[1052:78156] 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(1, supercls->ISA()->alignedInstanceSize() + extraBytes);
 7         meta = (Class) calloc(1, supercls->ISA()->ISA()->alignedInstanceSize() + extraBytes);
 8     } else {  //无父类
 9         cls = (Class) calloc(1, sizeof(objc_class) + extraBytes);
10         meta = (Class) calloc(1, sizeof(objc_class) + extraBytes);
11     }
12 
13     objc_initializeClassPair(supercls, name, cls, meta);
14     return cls;
15 }
16 Class objc_initializeClassPair(Class supercls, const char *name, Class cls, Class meta)
17 {
18     // Connect to superclasses and metaclasses
19     cls->initIsa(meta);
20     set_superclass(cls, supercls, YES);
21 
22     // Set basic info
23     cls->name = strdup(name);
24     meta->name = strdup(name);
25     cls->version = 0;
26     meta->version = 7;
27     cls->info = CLS_CLASS | CLS_CONSTRUCTING | CLS_EXT | CLS_LEAF;
28     meta->info = CLS_META | CLS_CONSTRUCTING | CLS_EXT | CLS_LEAF;
29 
30     // Set instance size based on superclass.
31     if (supercls) {
32         cls->instance_size = supercls->instance_size;
33         meta->instance_size = supercls->ISA()->instance_size;
34     } else {
35         cls->instance_size = sizeof(Class);  // just an isa
36         meta->instance_size = sizeof(objc_class);
37     }
38     
39     // No ivars. No methods. Empty cache. No protocols. No layout. Empty ext.
40     cls->ivars = nil;
41     cls->methodLists = nil;
42     cls->cache = (Cache)&_objc_empty_cache;
43     cls->protocols = nil;
44     cls->ivar_layout = &UnsetLayout;
45     cls->ext = nil;
46     allocateExt(cls);
47     cls->ext->weak_ivar_layout = &UnsetLayout;
48 
49     meta->ivars = nil;
50     meta->methodLists = nil;
51     meta->cache = (Cache)&_objc_empty_cache;
52     meta->protocols = nil;
53     meta->ext = nil;
54     
55     return cls;
56 }

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), 1);
10         if (old) memcpy(cls->ivars, old, oldSize);
11         if (old  &&  malloc_size(old)) free(old);
12         cls->ivars->ivar_count = newCount;
13         ivar = &cls->ivars->ivar_list[newCount-1];
14 
15         // 设置新添加变量的内容
16         ivar->ivar_name = strdup(name);
17         ivar->ivar_type = strdup(type);
18         ivar->ivar_offset = (int)cls->instance_size;
19 
20         cls->instance_size += (long)size;  // 加长对象实例的大小
21 
22     return result;
23 }

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 三实例之间的关系

注意:

  • root class为基类,一般NSObject为基类,若创建的类不基础任何类,则所创建的类为基类。
  • 所有元类的isa指针都指向root class的元类实例,包括root class meta本身。
  • class meta的superclass指针指向父类的meta,但root class meta的superclass指针则指向root class。
  • 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 = 1; i < 5; i++)
 8     {
 9         NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
10         currentClass = object_getClass(currentClass);
11     }
12     
13     NSLog(@"NSObject‘s class is %p", [NSObject class]);
14     NSLog(@"NSObject‘s meta class is %p", object_getClass([NSObject class]));
15 }
16 -(void) report
17 {
18     Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
19     class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "[email protected]:");//"[email protected]:"用于描述所添加方法的参数类型。v表示方法的返回参数类型,@表示第一个参数的类型,:表示第二个参数的类型。更多类型可参考Objective-C Runtime Programming Guide > Type Encodings.
20     objc_registerClassPair(newClass);
21     
22     id instanceOfNewClass = [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
23     [instanceOfNewClass performSelector:@selector(report)];
24     [instanceOfNewClass release];
25 }
26 -(void) main
27 {
28     [self report];
29     return;
30 }
31 2016-02-22 19:37:41.173 runtimeProject[882:81773] This object is 0x7fd5c8c02d30.
32 2016-02-22 19:37:41.174 runtimeProject[882:81773] Class is RuntimeErrorSubclass, and super is NSError.
33 2016-02-22 19:37:41.174 runtimeProject[882:81773] Following the isa pointer 1 times gives 0x7fd5c8c03180
34 2016-02-22 19:37:41.174 runtimeProject[882:81773] Following the isa pointer 2 times gives 0x7fd5c8c048c0
35 2016-02-22 19:37:41.174 runtimeProject[882:81773] Following the isa pointer 3 times gives 0x103ada198
36 2016-02-22 19:37:41.175 runtimeProject[882:81773] Following the isa pointer 4 times gives 0x103ada198
37 2016-02-22 19:37:41.175 runtimeProject[882:81773] NSObject‘s class is 0x103ada170
38 2016-02-22 19:37:41.175 runtimeProject[882:81773] 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 = 0;
 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 = 0; 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 = 0; 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 = 0; 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 = 0; 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 2016-02-22 19:51:02.403 runtimeProject[1017:91880] class name: MyClass
 99 2016-02-22 19:51:02.404 runtimeProject[1017:91880] ==========================================================
100 2016-02-22 19:51:02.404 runtimeProject[1017:91880] super class name: NSObject
101 2016-02-22 19:51:02.404 runtimeProject[1017:91880] ==========================================================
102 2016-02-22 19:51:02.404 runtimeProject[1017:91880] MyClass is not a meta-class
103 2016-02-22 19:51:02.404 runtimeProject[1017:91880] ==========================================================
104 2016-02-22 19:51:02.404 runtimeProject[1017:91880] MyClass‘s meta-class is MyClass
105 2016-02-22 19:51:02.405 runtimeProject[1017:91880] ==========================================================
106 2016-02-22 19:51:02.405 runtimeProject[1017:91880] instance size: 24
107 2016-02-22 19:51:02.405 runtimeProject[1017:91880] ==========================================================
108 2016-02-22 19:51:02.405 runtimeProject[1017:91880] instance variable‘s name: _array at index: 0
109 2016-02-22 19:51:02.405 runtimeProject[1017:91880] instance variable‘s name: _string at index: 1
110 2016-02-22 19:51:02.405 runtimeProject[1017:91880] instace variable _string
111 2016-02-22 19:51:02.405 runtimeProject[1017:91880] ==========================================================
112 2016-02-22 19:51:02.406 runtimeProject[1017:91880] property‘s name: array
113 2016-02-22 19:51:02.406 runtimeProject[1017:91880] property‘s name: string
114 2016-02-22 19:51:02.406 runtimeProject[1017:91880] property array
115 2016-02-22 19:51:02.406 runtimeProject[1017:91880] ==========================================================
116 2016-02-22 19:51:02.406 runtimeProject[1017:91880] method‘s signature: method1
117 2016-02-22 19:51:02.407 runtimeProject[1017:91880] method‘s signature: method2
118 2016-02-22 19:51:02.407 runtimeProject[1017:91880] method‘s signature: method3WithArg1:arg2:
119 2016-02-22 19:51:02.407 runtimeProject[1017:91880] method‘s signature: setArray:
120 2016-02-22 19:51:02.407 runtimeProject[1017:91880] method‘s signature: string
121 2016-02-22 19:51:02.407 runtimeProject[1017:91880] method‘s signature: setString:
122 2016-02-22 19:51:02.407 runtimeProject[1017:91880] method‘s signature: array
123 2016-02-22 19:51:02.408 runtimeProject[1017:91880] method method1
124 2016-02-22 19:51:02.408 runtimeProject[1017:91880] class method : classMethod1
125 2016-02-22 19:51:02.408 runtimeProject[1017:91880] MyClass is responsd to selector: method3WithArg1:arg2:
126 2016-02-22 19:51:02.408 runtimeProject[1017:91880] call method method1
127 2016-02-22 19:51:02.409 runtimeProject[1017:91880] ==========================================================
128 2016-02-22 19:51:02.409 runtimeProject[1017:91880] protocol name: NSCopying
129 2016-02-22 19:51:02.409 runtimeProject[1017:91880] protocol name: NSCoding
130 2016-02-22 19:51:02.409 runtimeProject[1017:91880] MyClass is responsed to protocol NSCoding
131 2016-02-22 19:51:02.410 runtimeProject[1017:91880] ==========================================================

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小节。

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

1 float a[] = {1.0, 2.0, 3.0};
2 NSLog(@"array encoding type: %s", @encode(typeof(a)));
3 2016-02-25 20:00:28.489 runtimeProject[977:88513] 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()宏定义进行转换。    

  1. 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参数:响应消息的名字,即[email protected](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, "[email protected]:");
 9           return YES;  //表示找到,让其重新调用。
10     }
11     return [super resolveInstanceMethod:aSEL];  //若找不到,则往父类中查找是否存在aSEL消息的响应方法
12 }
13 @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
时间: 2024-10-20 00:11:15

Objective-C:runtime的相关文章

刨根问底Objective-C Runtime

http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective%5Bnil%5Dc-runtime-(2)%5Bnil%5D-object-and-class-and-meta-class/ 刨根问底Objective-C Runtime(1)- Self & Super 刨根问底Objective-C Runtime(2)- Object & Class & Meta Class 刨根问底Objective-C Runtime(

刨根问底Objective-C Runtime(2)- Object &amp; Class &amp; Meta Class

Chun Tips 专注iOS开发 刨根问底Objective-C Runtime(2)- Object & Class & Meta Class 上一篇笔记讲述了objc runtime中Self 和 Super的细节,本篇笔记主要是讲述objc runtime中关于Object & Class & Meta Class的细节. 习题内容 下面代码的运行结果是? @interface Sark : NSObject @end @implementation Sark @e

刨根问底Objective-C Runtime(1)- Self &amp; Super

刨根问底Objective-C Runtime(1)- Self & Super - Chun Tips Chun Tips 专注iOS开发 刨根问底Objective-C Runtime(1)- Self & Super 前言 关于Objective-C Runtime一篇好的文档 : Understanding the Objective-C Runtime 译文地址为: http://blog.cocoabit.com/blog/2014/10/06/yi-li-jieobjecti

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

http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-liang-yu-shu-xing/ 上一篇笔记讲述了objc runtime中消息和Category的细节,本篇笔记主要是讲述objc runtime的 成员变量和属性. 习题内容 下面代码会? Compile Error / Runtime Crash / NSLog…? @interface Sark

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

一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/message.h> #import "Person.h" /* 总结: 1: runtime:必须要导入头文件 <objc/message.h>,此头文件中已经引入了<objc/runtime.h> 任何方法调用本质:发送一个消息,用runtime发送消息.OC底层实现

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

#import "ViewController.h" /* Runtime(交换方法):主要想修改系统的方法实现 需求: 比如说有一个项目,已经开发了2年,忽然项目负责人添加一个功能,每次UIImage加载图片,告诉我是否加载成功 当系统提供的控件不能满足我们的需求的时候,我们可以 1:通过继承系统控件,重写系统的方法,来扩充子类的行为(super的调用三种情况) 2:当需要为系统类扩充别的属性或是方法的时候,与哪个类有关系,就为哪个类创建分类.3:利用runtime修改系统的类,增加

转载 理解objective c的Runtime

注:本文是对 Colin Wheeler 的 Understanding the Objective-C Runtime 的翻译. 初学 Objective-C(以下简称ObjC) 的人很容易忽略一个 ObjC 特性 —— ObjC Runtime.这是因为这门语言很容易上手,几个小时就能学会怎么使用,所以程序员们往往会把时间都花在了解 Cocoa 框架以及调整自己的程序的表现上.然而 Runtime 应该是每一个 ObjC 都应该要了解的东西,至少要理解编译器会把 [target doMeth

iOS:runtime最全的知识总结

runtime 完整总结 好东西,应该拿出来与大家分享... 南峰子博客地址:http://southpeak.github.io/blog/categories/ios/ 原文链接:http://www.jianshu.com/p/6b905584f536 类和对象 Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等.这种特性意味着

Objective-O Runtime 运行时初体验

Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等. 这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码.对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行.这个运行时系统即Objc Runtime.Objc Runtime其实是一个Runtime