前言
结论:ObjC是一种面向对象的编程语言,具有运行时的特性,是通过ObjC runtime来实现的,即尽可能把代码执行的决策从编译链接推迟到运行时。
与静态语言相比一个明显的例子:
#include < stdio.h >
int main(int argc, const char **argv[])
{
printf("Hello World!");
return 0;
}
编译后得到相应的汇编语言,再链接库,生成可执行代码。
对于ObjC中常见的消息发送:
编译器编译后得到:[self doSomethingWithVar:var1];
得到一个C的方法,传入了三个变量,仅此。objc_msgSend(self,@selector(doSomethingWithVar:),var1);
对于ObjC runtime来说,需要做的事就是加载类的信息,进行方法的分发和转发等操作。
接下来看一些基本的概念:
1、id的定义
对象结构体:
typedef struct objc_object {
Class isa;
} *id;任意类型(id)是一个objc_object结构类型的指针,所有的objc_object对象的结构体中都有一个isa指针,这个isa指向它所属的类,
运行时靠这个isa指针来检测这个对象是否可以响应一个selector。
2、Class的定义:
类结构体:
typedef struct objc_class *Class;
Class 是一个objc_class结构类型的指针;
接下来看objc_class的定义:
struct objc_class
{
struct objc_class* isa;
struct objc_class* super_class;
const char* name;
long version;
long info;
long instance_size;
struct objc_ivar_list* ivars;
struct objc_method_list** methodLists;
struct objc_cache* cache;
struct objc_protocol_list* protocols;
};解读isa
发现有两个isa,OC对类对象和实例对象中的isa所指向的类结构作了不同的命名:
类对象中的isa指向类结构被称作meta class,meta class存储类的static类成员变量与+开头的static类成员方法。
实例对象中的isa指向类结构被称作 class(就是平时所得该对象所属的类),class结构存储类的普通成员变量与-开头的普通成员方法。
解读super_class
顾名思义,super_class指向当前类的父类,特殊的是根类NSObject的super_class指向nil。
关于类 父类 根类 ,子类的isa 类的isa关系总结:
1、类的实例对象的isa指向该类,该类的isa指向该类的meta class;
2、类的super_class指向该类的父类,根类的super_class指向nil;
3、meta class 的isa 指向根类的meta class,根类的meta class 指向根类meta class自身。
4、meta class 的super_class指向父类的meta class,根类的meta class的super_class指向meta class 对应的类。
转载一张图,清晰明了:
专门说一下,class与meta class的区别:
class 是 实例对象的类型,向 实例对象 发送消息(调用实例方法)时,我们在该实例对象的class结构的methodlists(Class结构体中查看)中查找响应的函数,
如果没有找到则到该class的父类的methodlists中去查找,就这样一直找到跟类的methodlists,仍没有找到就会响应动态方法解析与消息转发机制。
meta class 是 类对象的类型,向 类对象 发送消息(调用类方法)时,我们在该类对象的meta class结构的methodlists中查找响应的函数,
如果没有找到则到该meta class的父类的methodlists中去查找,就这样一直找到跟类的methodlists,仍没有找到就会响应动态方法解析与消息转发机制。
name
一个c字符串,只是类的名称,可以通过这个名称查找到该类。
通过:id objc_getClass(const char *aClassName))或该类的 metaclass(id objc_getMetaClass(const char *aClassName)
instance_size
该类的实例变量大小包括父类继承下的。
ivars
指向 objc_ivar_list 的指针,存储每个实例变量的内存地址,如果该类没有任何实例变量则为 NULL。
实例变量是通过基类地址加变量位置偏移得到的。
info:
供运行期使用的一些位标识。
methodLists:
与info的一些标志位有关,如果 info 设置了 CLS_CLASS 则 objc_method_list 存储实例方法,如果设置的是 CLS_META 则存储类方法。
cache:
指向 objc_cache 的指针,用来缓存最近使用的方法,以提高效率。
protocol:
指向 objc_protocol_list 的指针,存储该类声明要遵守的正式协议。
3、objc_msgSend定义
id objc_msgSend(id self, SEL op, ...)
objc_selector是一个根据方法的名字来区分方法的唯一ID,只要名字相同selector就相同。typedef struct objc_selector *SEL;
不同的类可以拥有相同的selector,不同类的实例对象执行相同的selector时,会在各自的方法列表中根据selector去寻找自己对应的IMP。
4、IMP定义
IMP本质就是一个函数指针,这个被指向的函数包含一个接收消息的对象id,调用方法的SEL,以及一些方法参数,并返回一个id。typedef id (*IMP)(id, SEL, ...);
因此我们可以通过SEL获得它所对应的IMP,在取得了函数指针之后,也就意味着我们取得了需要执行方法的代码入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。
5、msg_sender工作流程
在Objective-C中,消息直到运行时才会绑定到方法的实现上。编译器会把代码中
[target doSth]
转换成objc_msgSend
消息函数,这个函数完成了动态绑定的所有事情。它的运行流程如下:
- 检查selector是否需要忽略。(ps: Mac开发中开启GC就会忽略retain,release方法。)
- 检查target是否为nil。如果为nil,直接cleanup,然后return。(这就是我们可以向nil发送消息的原因。)
- 然后在target的Class中根据Selector去找IMP
寻找IMP的过程:
- 先从当前class的cache方法列表(cache methodLists)里去找
- 找到了,跳到对应函数实现
- 没找到,就从class的方法列表(methodLists)里找
- 还找不到,就到super class的方法列表里找,直到找到基类(NSObject)为止
- 最后再找不到,就会进入动态方法解析和消息转发的机制。
objc_method_list存储当前类的方法链表,里面包含objc_method,它存储了类的某个方法的信息。 typedef struct objc_method *Method;
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE;
结论,objct_class中的method_list保存了一组SEL<->IMP的关系映射表。
6、动态方法解析和消息转发机制
当在最终的MethodLists里仍找不到响应方法时,系统提供三次机会,否则抛出异常,程序崩溃。① 动态方法解析 +(Bool)resolveInstanceMethod:(SEL)sel
举例:通过sel判断满足条件后,增加一个响应方法,并返回YES
②<pre name="code" class="objc">+(Bool)resolveInstanceMethod:(SEL)sel { if(sel == @selector(method:)){ class_addMethod([self class],sel,(IMP)newMethod,"v@:"); } <code class="js spaces"></code><code class="js keyword">return</code> <code class="js plain">[</code><code class="js keyword">super</code> <code class="js plain">resolveInstanceMethod:sel];</code> }
备用接受者 - (id)forwardingTargetForSelector:(SEL)aSelector;
返回一个能响应该方法的target对象
③ 消息转发对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。- (void)forwardInvocation:(NSInvocation *)anInvocation
我们可以在forwardInvocation方法中选择将消息转发给其它对象。
我们必须重写以下方法:
1
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|