消息转发

  • 消息转发的一些源码分析
1
2
3
4
5
6
7
8
//正常调用一个方法都会转化成这个函数
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
//获取方法的实现,通过isa指针,去类里用SEL找方法
//同时记住,SEL其实也就是字符串罢了
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}
  • 接下来去到方法寻找函数里
1
2
3
4
5
6
7
8
9
10
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
//真正的寻找方法在下面这个函数里,这里负责防止类或方法为空
IMP imp = lookUpImpOrNil(cls, sel);
//如若没有找到方法实现,那么就进入了我们熟悉的下一个消息转发机制
if (!imp) return _objc_msgForward;
//_objc_msgForward 用于消息转发
return imp;
}
  • 查找方法实现的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
IMP lookUpImpOrNil(Class cls, SEL sel) {
//如果类没有初始化,那么初始化它,熟悉的懒加载呀
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = cls;
IMP imp = nil;
do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
if (!curClass) break;//如果类不存在,跳出
if (!curClass->cache) fill_cache(cls, curClass);//创建缓存,也是懒加载
//在缓存中查找方法
imp = cache_getImp(curClass, sel);
//找到跳出
if (imp) break;
} while (curClass = curClass->superclass);//一直向父类找,最终为空时跳出,NSObeject类的父类就为空,元类不为空
return imp;
}
  • Apple没有公开_objc_msgForward的实现源码,_objc_msgForward是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
  • _objc_msgForward消息转发做的几件事:
1
2
3
4
5
1.调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。
2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。
4.调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
5.调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。
  • 上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
  • 直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
  • _objc_msgForward隶属 C 语言,有三个参数 :
1
2
3
1.所属对象 ->id类型
2.方法名 ->SEL类型
3.可变参数 ->可变参数类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
那么 objc_msgSend 到底是怎么工作的呢
在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)为止
最后再找不到,就会进入动态方法解析和消息转发的机制。