之前的文章中,我们理解了消息发送(objc_msgSend),具体地址点这里,并且遗留了一个问题:如果对象收到无法解读的消息之后,会发什么?今天,我们就来解决下.
在编译期给类发送了无法理解的消息并不会报错,因为在运行期间可以向类增加方法,编译器在编译时无法确定是否有对应的方法实现,当对象收到无法解读的消息后,就会启动消息转发(message forwarding).以下是示例代码:
@interface People : NSObject
+ (void)func;
@end
@implementation People
@end
首先我们在People中声明了func方法,但是并未实现.这时,我们去调用此方法
[People func];
看下控制台,会报错
+[People func]: unrecognized selector sent to class 0x102d5a840
消息转发(message forwarding)分为两大阶段,第一阶段:先询问接受者所属的类,看其能否动态添加方法,处理当前未知的选择子(unrecognized selector),这叫做动态方法解析(dyanmic method resolution).第二阶段设计完整的消息转发机制(full forwarding mechanism),如果运行期已经把第一阶段执行完了,那么接受者无法再用动态新增的方法的手段来响应包含该选择子的消息了.此时,运行期系统会请求接受者以其他手段处理与消息相关的方法调用.
这又分为2小步:首先,看下有没有其他对象能处理这条消息.如果有,把消息转给那个对象,消息转发过程结束.如果没有,则启动完整的消息转发机制,运行时系统会发消息的全部细节封装到NSInvoction对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息.
动态方法解析
对象在收到无法解读的消息后,首先调用所属类的下列类方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
sel就是未知的选择子,返回值是BOOL类型,表明这个类能否新增一个类实例方法处理此选择子,在继续往下执行转发机制之前,本类有机会新增一个处理此选择子的方法.如果尚未实现的方法不是实例方法,而是对象方法,那么运行期系统会调用另外一个方法
+ (BOOL)resolveClassMethod:(SEL)sel;
使用这种方法的前提是:相关方法的实现代码已写好,只等会运行时动态插入到类里面就好.
后备接受者
当前接受者还有第二次机会处理未知的选择子,在此期间,运行期系统会问它:能否把这条消息转给奇特接受者来处理,与这步对应的方法是:
- (id)forwardingTargetForSelector:(SEL)aSelector;
如果能找到后备接受者,则返回它,如果不能,则返回nil,
需要注意的是:我们无法操作这一步骤转发的消息,如果想在发给后备接受者之前修改消息内容,那么就要用到完整的消息转发机制.
完整的消息转发
如果来到这一步,那么首先要创建NSInvocation对象,把待处理的消息内容细节全部封装在内部,这个对象包含选择子,目标和参数,再出发NSInvocation对象时,消息转发系统(message-dispatch system)将亲自出马,将消息指派给目标对象.用到的方法如下:
- (void)forwardInvocation:(NSInvocation *)anInvocation
这个方法很简单,只需要改变调用目标,使消息在新目标上得以调用即可.这样实现的效果与"后备接受者"的效果一样,所以,很少有人这样做,常用的做法,是改变下消息内容,比如改变选择子,增加一个参数,然后再执行转发.
如果本类不能响应此方法,那么需要调用父类的同名方法,如果父类;诶仍然不能响应此方法,那么直至NSObject类,如果调用了NSObject类的方法,那么还会继续调用doesNotRecoginzeSelector: 然后跑出异常,这个异常就表示选择子没有得到处理.