我们都知道,在oc中给一个对象发消息,其实是其调用objc_msgSend。
objc_msgSend调用过程中做了下面几件事
1.nilTest
2.cacheLookup
3.MethodTableLookup
4.lookupImpOrForward
5.查找IMP
![](https://img.haomeiwen.com/i1610813/96a58e6670737481.png)
当方法没找到的时候,runtime会进入消息转发机制
1.动态消息决议
- resolveClassMethod:(SEL)sel;
- resolveInstanceMethod:(SEL)sel;
这两方法会带上SEL类型的参数,就是方法名的一个字符串。我们可以在这里通过class_addMethod去添加IMP,返回之后,runtime会根据当前sel再去找一遍IMP,如果还是没找到的话,会再进入第2阶段
2.快转发
- (id)forwardingTargetForSelector
这里比较简单,返回一个可以实现这个方法的对象,如果返回 [super forwardingTargetForSelector] 或者nil,都会让runtime进入下一个阶段
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
第一个是返回一个NSMethodSignature对象,这个对象记录了方法的返回值和参数类型,不记录sel,就是一个方法的模板,return nil ,runtime会直接抛异常
第二个 NSInvocation就是由第一个方法返回的对象初始化出来的,如果我们不实现这个方法,runtime会直接抛出异常
方法安全气垫
安全气垫就是指拦截所有类似unrecognized selector sent to instance xxx 的崩溃,本质就是利用的消息转发机制。
大多数安全气垫都采用forwardingTargetForSelector 这一层来拦截,通过AOP的方式,交换原来的实现,然后指向自定义的类。再在自定义的类里面直接在动态消息决议里面添加IMP就可以了,这个IMP的实现可以做一些日志的记录或者上报
为啥采用forwardingTargetForSelector ,相关blog是这么解释
1.resolveInstanceMethod 需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余的
2.forwardInvocation可以通过NSInvocation的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation对象,并且forwardInvocation的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写
3.forwardingTargetForSelector可以将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写,而且我们是交换NSObject的forwardingTargetForSelector实现,不会覆盖已重写的类。
网友评论