Objective-C Runtime 运行时(一):类与对象

来源:互联网 发布:压缩路径的加权算法 编辑:程序博客网 时间:2024/06/17 13:29

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

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

Runtime库主要做下面几件事:

1、封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。
2、找出方法的最终执行代码:当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。

类与对象基础数据结构


Class

类本质上是一个结构体,Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:

typedef struct objc_class *Class;
通过查看objc/runtime.h中objc_class结构体的定义如下:
    struct objc_class {              Class isa; // 指向metaclass(metaclass 后面解释)               Class super_class ;   // 指向其父类              const charchar *name ;    // 类名              long version ;    // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取              long info;   // 一些标识信息             long instance_size ;   // 该类的实例变量大小(包括从父类继承下来的实例变量);              struct objc_ivar_list *ivars;   // 成员变量链表,通过ivars可以访问所有的成员变量              struct objc_method_list **methodLists ;   // 存储类的方法链表              struct objc_cache *cache;   // 指向最近使用的方法列表,用于提升运行效率;              struct objc_protocol_list *protocols;   // 协议链表       }       /* Use `Class` instead of `struct objc_class */ 

objc_object与id

objc_object是表示一个类的实例的结构体,它的定义如下(objc/objc.h):

struct objc_object {    Class isa  OBJC_ISA_AVAILABILITY;};typedef struct objc_object *id;

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

当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。

另外还有我们常见的id,其实它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的指针可以指向任何类型的指针,也就是说也接受任何类型的对象,有点类似于C语言中void *指针类型的作用,可以指向任何数据类型的内存。

objc_cache

上面提到了objc_class结构体中的cache字段,它用于缓存调用过的方法。这个字段是一个指向objc_cache结构体的指针,其定义如下

struct objc_cache {    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;    unsigned int occupied                                    OBJC2_UNAVAILABLE;    Method buckets[1]                                        OBJC2_UNAVAILABLE;};

该结构体的字段描述如下:

1、mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。
2、occupied:一个整数,指定实际占用的缓存bucket的总数。
3、buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。

元类(Meta Class)

在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。如:

NSArray *array = [NSArray array];
这个例子中,+array消息发送给了NSArray类,而这个NSArray也是一个对象。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针。那么这些就有一个问题了,这个isa指针指向什么呢?为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class是一个类对象的类

当向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。

通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下图所示:

下面通过例子来说明class、superClass、MetalClass

#import "TestRuntime.h"#import <objc/runtime.h>@implementation TestRuntime/** *  类加载的时候会调用 */+ (void)load{    [super load];        [self registerClassPair];}+ (void)registerClassPair{    Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);    class_addMethod(newClass, @selector(testMetaClass), (IMP)TestMetaClass, "v@:");    objc_registerClassPair(newClass);        id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];    [instance performSelector:@selector(testMetaClass)];}void TestMetaClass(id self, SEL _cmd) {        NSLog(@"This objcet is %p", self);    NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);        Class currentClass = [self class];    for (int i = 0; i < 4; i++) {        NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);        currentClass = objc_getClass((__bridge void *)currentClass);    }        NSLog(@"NSObject's class is %p", [NSObject class]);    NSLog(@"NSObject's meta class is %p", objc_getClass((__bridge void *)[NSObject class]));}@end
上面的例子是在运行时创建了一个NSError的子类TestClass,然后为这个子类添加一个方法testMetaClass,这个方法的实现是TestMetaClass函数。

运行后,打印结果是

2015-08-30 21:16:58.415 runtime[4996:195361] This objcet is 0x1003116302015-08-30 21:16:58.416 runtime[4996:195361] Class is TestClass, super class is NSError2015-08-30 21:16:58.416 runtime[4996:195361] Following the isa pointer 0 times gives 0x1003114002015-08-30 21:16:58.416 runtime[4996:195361] Following the isa pointer 1 times gives 0x02015-08-30 21:16:58.416 runtime[4996:195361] Following the isa pointer 2 times gives 0x02015-08-30 21:16:58.417 runtime[4996:195361] Following the isa pointer 3 times gives 0x02015-08-30 21:16:58.417 runtime[4996:195361] NSObject's class is 0x7fff732810f02015-08-30 21:16:58.417 runtime[4996:195361] NSObject's meta class is 0x0

我们在for循环中,我们通过objc_getClass来获取对象的isa,并将其打印出来,依此一直回溯到NSObject的meta-class。分析打印结果,可以看到最后指针指向的地址是0x0,即NSObject的meta-class的类地址。

这里需要注意的是:我们在一个类对象调用class方法是无法获取meta-class,它只是返回类而已。

1 0