Runtime简介
Runtime 又叫运行时,是一套底层的 C 语言 API,是 iOS 系统的核心之一。开发者在编码过程中,可以给任意一个对象发送消息,在编译阶段只是确定了要向接收者发送这条消息,而接受者将要如何响应和处理这条消息,那就要看运行时来决定了。
OC语言在编译期都会被编译为C语言的Runtime代码,二进制执行过程中执行的都是C语言代码。而OC的类本质上都是结构体,在编译时都会以结构体的形式被编译到二进制中。Runtime是一套由C、C++、汇编实现的API,所有的方法调用都叫做发送消息。
C语言中,在编译期,函数的调用就会决定调用哪个函数。
而OC的函数,属于动态调用过程,在编译期并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用。
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。
Runtime使用
Runtime是一个共享动态库,其目录位于/usr/include/objc,由一系列的C函数和结构体构成。和Runtime系统发生交互的方式有三种,一般都是用前两种:
- 使用OC源码
直接使用上层OC源码,底层会通过Runtime为其提供运行支持,上层不需要关心Runtime运行。 - NSObject
在OC代码中绝大多数的类都是继承自NSObject的,NSProxy类例外。Runtime在NSObject中定义了一些基础操作,NSObject的子类也具备这些特性。 - Runtime动态库
上层的OC源码都是通过Runtime实现的,我们一般不直接使用Runtime,直接和OC代码打交道就可以。
使用Runtime需要引入下面两个头文件,一些基础方法都定义在这两个文件中。
1 |
关于库函数可以在Objective-C Runtime Reference中查看 Runtime 函数的详细文档。
关于这一点,其实还有一个小插曲。当我们导入了objc/Runtime.h和objc/message.h两个头文件之后,我们查找到了Runtime的函数之后,代码打完,发现没有代码提示了,那些函数里面的参数和描述都没有了。对于熟悉Runtime的开发者来说,这并没有什么难的,因为参数早已铭记于胸。但是对于新手来说,这是相当不友好的。而且,如果是从iOS6开始开发的同学,依稀可能能感受到,关于Runtime的具体实现的官方文档越来越少了?可能还怀疑是不是错觉。其实从Xcode5开始,苹果就不建议我们手动调用Runtime的API,也同样希望我们不要知道具体底层实现。所以IDE上面默认代了一个参数,禁止了Runtime的代码提示,源码和文档方面也删除了一些解释。
如果发现导入了两个库文件之后,仍然没有代码提示,就需要把这里的设置改成NO,即可。
NSObject介绍
在OC的世界中,除了NSProxy类以外,所有的类都是NSObject的子类。在Foundation框架下,NSObject和NSProxy两个基类,定义了类层次结构中该类下方所有类的公共接口和行为。NSProxy是专门用于实现代理对象的类,这个类暂时本篇文章不提。这两个类都遵循了NSObject协议。在NSObject协议中,声明了所有OC对象的公共方法。
1 | @protocol NSObject |
objc_class的源码如下:
1 |
|
在这里可以看到,在一个类中,有超类的指针,类名,版本的信息。ivars是objc_ivar_list成员变量列表的指针;methodLists是指向objc_method_list指针的指针。*methodLists是指向方法列表的指针。这里如果动态修改*methodLists的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。
在NSObject的类中还定义了一个方法
1 | + (IMP)instanceMethodForSelector:(SEL)aSelector; |
IMP则引出了另一个概念,这个后面会介绍,我们继续说NSObject。

图中实线是 super_class指针,虚线是isa指针。
Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。- 每个Class都有一个isa指针指向唯一的Meta class
Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。- 每个Meta class的isa指针都指向Root class (meta)。
我们其实应该明白,类对象和元类对象是唯一的,对象是可以在运行时创建无数个的。而在main方法执行之前,从 dyld到runtime这期间,类对象和元类对象在这期间被创建。
具体的实现需要看源代码,这里我就不讨论源代码的内容了。
相关概念
上面介绍了NSObject,下面介绍一下其他的相关概念。
IMP
在Runtime中IMP本质上就是一个函数指针,其定义如下。在IMP中有两个默认的参数id和SEL,id也就是方法中的self,这和objc_msgSend()函数传递的参数一样。
Runtime中提供了很多对于IMP操作的API,下面就是不分IMP相关的函数定义。我们比较常见的是method_exchangeImplementations函数,Method Swizzling就是通过这个API实现的。
1 | /// A pointer to the function of a method implementation. |
Method
Method用来表示方法,其包含SEL和IMP,下面可以看一下Method结构体的定义。
1 | typedef struct method_t *Method; |
在Xcode进行编译的时候,只会将Xcode的Compile Sources中.m声明的方法编译到Method List,而.h文件中声明的方法对Method List没有影响。
Property
在Runtime中定义了属性的结构体,用来表示对象中定义的属性。@property修饰符用来修饰属性,修饰后的属性为objc_property_t类型,其本质是property_t结构体。其结构体定义如下。
1 | typedef struct property_t *objc_property_t; |
可以通过下面两个函数,分别获取实例对象的属性列表,和协议的属性列表。
1 | objc_property_t * class_copyPropertyList(Class cls,unsigned int * outCount) |
可以通过下面两个方法,传入指定的Class和propertyName,获取对应的objc_property_t属性结构体。
1 | objc_property_t class_getProperty(Class cls,const char * name) |