怎么挽救unrecognized selector异常——消息转发机制

[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:消息给目标对象。

以上摘自《招聘一个靠谱的 iOS》—参考答案(上)第18题

总而言之,我们可以在自己的类重写上述的方法,阻挡程序走到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调用所以不能用

你可能感兴趣的:(*,错误解决方案,*,iOS基础)