1.编译阶段
用户调用一个普通函数[anObject doTings:things];
编译器会把其编译为:objc_msgSend(anObject, @selector(doThings:), things);
注意:根据函数返回值的不同,编译器还会将其编译为objc_msgSend_stret、objc_msgSend_fpret等。但是,实现大同小异。这里只以objc_msgSend为例进行讨论。
2.msgSend概述
objc_msgSend实现大致如下:id objc_msgSend(id self, SEL _cmd, ...){
Class class = object_getClass(self;)
IMP imp = cache_lookup(c, _cmd);
if(!imp){
imp = class_getMethodImplementation(class, _cmd);
}
return imp ? imp(self, _cmd, ...) : 0
}
由此可见,其主要功能可分为获取函数imp和调用imp两块。
3.msgSend整体流程
Step1. dispatch table中搜索selector的imp。这步大家应该都清楚,子类找不到,就去搜索父类,依次往上遍历。
注:dispatch table是编译时生成的selector与imp的对应表
Step2. 如果子类和所有父类的dispatch table中都没找到,就到动态解析。
很多人可能认为,这个曲折的寻找过程是必不可少的。其实并不是这样的。可以将子类中某个方法的imp直接设置成objc_msgForward,就可以直接进入转发流程。
Step3.函数调用
这里着重介绍下转发机制(__forwarding__)的实现。其代码大致如下:
这里不做详细分析,简要说下我认为有用的几点:
a.为何有了forwardingInvoaction,还需要fowardingTarget?
因为fowardingTarget不涉及参数解析与封装,只是简单的把target替换下,就立马进入下一轮msgSend,所以效率较高,是首选。只有当涉及参数处理时,才有必要在forwardingInvoaction中干。
b.为什么forwardingInvoaction前,需要获取methodSignature?
单靠selector,无法完成对参数的解析,从而无法完成对target、selector和参数的打包。此时,需要获取method signature(包含参数、返回值的所有信息)来协助完成对参数的解析。
objc_msgForward和其他函数imp一样,都是函数指针
objc_msgForward和其他函数imp一样,都是函数指针
objc_msgForward和其他函数imp一样,都是函数指针
重要的事情说三遍
参考文献
[1] What's that Selector?
[2] Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法