Runtime
Runtime 是一个用C写的运行时库,主要为 C 添加了面向对象的能力并创造了 Objective-C,并且拥有消息发送,消息转发等功能。
Runtime源码苹果是公开的,希望看着源码来理解苹果设计的思想,本文下载的是objc4-532.tar.gz,不要下载太新的,新版runtime结合了swift语言运行需要的环境,底层源码改版较多。
Runtime 涉及三个点,面向对象,消息发送,消息转发。
我们都知道高级编程语言想要成为可执行文件需要先编译为汇编语言,再汇编为机器语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。C语言是面向过程开发的,OC能够进行面向对象开发主要依靠Runtime,Runtime将
OC语言中面向对象的类
转换为C语言面向过程的结构体
,这个转换过程就是我们所要讲的Runtime。
面向对象
面向对象编程中,最重要的概念就是类,NSObjcet是OC语言中对象的基类(NSProxy除外),所有对象都要继承NSObjcet,下面我们就从代码入手,看看OC是如何实现类的?
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
在NSObjcet源码,只能看到一个Class的isa指针,isa指针指向该类的结构体,我们看看Class:
typedef struct objc_class *Class;
struct objc_class {
Class isa; // 指向metaclass
Class super_class ; // 指向父类,如果这个类是根类,则为NULL
const char *name ; // 类名
long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols; // 存储该类遵守的协议
}
在这个objc_class结构体中,我们发现类定义所需要的必要信息,名称、父类、成员变量列表、方法列表等。这里的信息多数看字面都能理解,只有Class isa
这个类型需要特别关注,它是Runtime里定义数据对象关系的核心。
- 对象实例化以后会将实例的isa指针指向
对象Class
,无论初始化多少个实例,其isa指针
都会指向同一个对象Class
,这个Class中存储普通成员变量和对象方法(“-”开头的方法)。 -
对象Class
会将自身的isa指针指向一个静态Class(metaclass)
,该实例专门管理静态成员变量和静态方法。
Objective-C 类也是对象,runtime 通过创建 Meta Classes 来处理这些。当你发送一个消息像这样 [NSObject alloc] 你正在向类对象发送一个消息,这个类对象需要是 MetaClass 的实例,MetaClass 也是 root meta class 的实例。当你说继承自 NSObject 时,你的类指向 NSObject 作为自己的 superclass。然而,所有的 meta class 指向 root metaclass 作为自己的 superclass。所有的 meta class 只是简单的有一个自己响应的方法列表。所以当你向一个类对象发送消息如 [NSObject alloc],然后实际上 objc_msgSend() 会检查 meta class 看看它是否响应这个方法,如果他找到了一个方法,就在这个 Class 对象上执行(译注:class 是一个实例对象的类型,Class 是一个类(class)的类型。对于完全的 OO 来说,类也是个对象,类是类类型(MetaClass)的实例,所以类的类型描述就是 meta class)。
为什么我们继承自苹果的类
从你开始 Cocoa 开发时,那些教程就说如继承自 NSObject 然后开始写一些代码,你享受了很多继承自苹果的类所带来的便利。有一件事你从未意识到的是你的对象被设置为使用 Objective-C 的 runtime。当我们为我们的类的一个实例分配了内存,像这样…
MyObject *object = [[MyObject alloc] init];
最先执行的消息是 +alloc。如果你查看下文档, 它说“新的实例对象的 isa 实例变量被初始化为指向一个数据结构,那个数据结构描述了这个类;其他的实例变量被初始化为 0。”所以继承自苹果的类不仅仅是继承了一些重要的属性,也继承了能在内存中轻松分配内存的能力和在内存中创建满足 runtime 期望的对象结构(设置 isa 指针指向我们的类)。
那么 Class Cache 是什么?(objc_cache *cache)
当 Objective-C runtime 沿着一个对象的 isa 指针检查时,它会发现一个对象实现了许多的方法。然而你可能只调用其中一小部分的方法,也没有意义每次检查时搜索这个类的分发表(dispatch table)中的所有 selector。所以这个类实现了一个缓存,当你搜索一个类的分发表,并找到合适的 selector 后,就会把它放进缓存中。所以当 objc_msgSend() 在一个类中查找 selector 时会先查找类缓存。有个理论是,当你在一个类上调用了一个消息,你很可能之后还会调用它。所以如果我们考虑到这点,就意味着当我们有个子类继承自 NSObject 叫做 MyObject 并且运行了以下的代码
alloc 方法会为对象分配一块内存空间,空间的大小为 isa_t(8 字节)的大小加上所有成员变量所需的空间,再进行一次内存对齐。分配完空间后会初始化 isa_t ,而 isa_t 是一个 union 类型的结构体(或者称之为联合体),它的结构是在 Runtime 里被定义的。
MyObject *obj = [[MyObject alloc] init];
发生了以下的事:
(1) [MyObject alloc] 首先被执行。MyObject 没有实现 alloc 方法,所以我们不能在这个类中找到 +alloc 方法,然后沿着 superclass 指针会指向 NSObject。
(2) 我们询问 NSObject 是否响应 +alloc 方法,它可以。+alloc 检查消息的接收者类,是 MyObject,然后分配一块和我们的类同样大小的内存空间,并初始化它的 isa 指针指向 MyObject 类,我们现在有了一个实例对象,最终把类对象的 +alloc 方法加入 NSObject 的类缓存(class cache)中(lastly we put +alloc in NSObject's class cache for the class object )。
(3) 到现在为止,我们发送了一个类消息,但是现在我们发送一个实例消息,只是简单的调用 -init 或者我们设计的初始化方法。当然,我们的类会响应这个方法,所以 -(id)init 加入到缓存中。(译注:要是 MyObject 实现了 init 方法,就会把 init 方法加入到 MyObject 的 class cache 中,要是没有实现,只是因为继承才有了这个方法,init 方法还是会加入到 NSObject 的 class cache 中)。
(4) 然后 self = [super init] 被调用。super 是个 magic keyword,指向对象的父类,所以我们得到了 NSObject 并调用它的的 init 方法。这样可以确保 OOP(面相对象编程) 的继承功能正常,这个方法可以正确的初始化父类的变量,之后你(在子类中)可以初始化自己的变量,如果需要可以覆盖父类的方法。在 NSObject 的例子中,没什么重要的要做,但并不总是这样。有时要做些重要的初始化。比如…
参考文章:
iOS 开发:『Runtime』详解(一)基础知识
Runtime之消息发送和消息转发
深入浅出Runtime (一) 什么是Runtime? 定义
深入浅出Runtime (二) Runtime的消息机制
深入浅出Runtime (三) Runtime的消息转发
深入浅出Runtime (四) Runtime的实际应用之一,字典转模型