[obj foo];在objc动态编译时,会被转意为:objc_msgSend(obj, @selector(foo));
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
Method resolution
objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。
Fast forwarding
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
Normal forwarding
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
总而言之,我们可以在自己的类重写上述的方法,阻挡程序走到doesNotRecognizeSelector
抛异常。
github上有个demo可以供下载试验:_objc_msgForward_demo
下面分别来解释下各个函数的具体写法:
//第一调用
+ (BOOL)resolveInstanceMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0) {
NSLog(@"resolveInstanceMethod - %s", sel);
class_addMethod(self.class, sel, (IMP)imp, "@:");
[super resolveInstanceMethod:sel];
return YES;
}
id imp(id self, SEL _cmd)
{
return nil;
}
其中最重要的就是class_addMethod()
方法,前几个参数都比较好理解,给self.class
加一个名为sel
,实现为imp
的方法。
最后一个参数”@:”表示新加方法的样式,@
表示self
,:
表示_cmd
(即SEL),注意看正好对应imp的两个参数(id self, SEL _cmd)
。
class_addMethod
最后一个参数一定要加且至少要包含@:
,除此之外可以在前后加额外的信息,比如v@:@
表示函数体为void xxx(id self, SEL _cmd,NSString* str)
,放在最前面表示return的类型,v
表示void
,@
表示字符串类型。
其他字符对应可以看这里《Type Encodings》。
resolveInstanceMethod
有个不好的地方,就是会截断一些本不会造成unrecognized selector异常的消息发送,这也是我调试时发现的,原因还不得而知。
未完。。。
第三种方法会影响Lua的wax调用所以不能用