上一篇文章初步讲了一下objc_msgSend
的做用,这里是传送门初步理解objc_msgSend 。那么,对象在收到消息以后没法经过objc_msgSend
发送的消息以后会怎么办呢?数据库
因为OC是动态语言,因此在运行时还能够继续向类中添加方法,因此当对象收到没法解读的消息时,就会启动消息转发
机制,咱们能够经由这个机制告诉程序该怎么处理这种消息。设计模式
消息转发会分红两大阶段,第一阶段叫作动态方法解析(dynamic method resolution):先征询当前接收者所属的类,是否能动态添加方法并处理这个未知的selector,若是接收者没有动态添加方法或者动态添加的方法依然不能处理这个未知的selector,则当前接收者本身就没有办法经过动态新增方法的手段来响应这个selector了,以后就进入消息转发的第二阶段。第二阶段能够分红两步,第一步接收者会查看是否存在其余对象能处理这条消息,若是有,则这个处理消息的对象叫备援接收者(replacement receiver),runtime系统会把消息转发给这个对象,消息转发流程结束。第二步,若是连备援接收者都没有,则启动完整的消息转发,runtime系统会把和消息有关的全部信息都放进NSInvocation对象中,再给接收者一次机会,处理未知的selector,若是这一步都失败了,就会抛出unrecognize selector send to instance xxx这个异常。post
看完上面这段文字,再结合下面这张消息转发流程图,应该就能对整个消息转发流程有个比较形象的认知了: spa
下面具体看看每一步的过程。设计
对象在收到没法解读的消息后,会先调用所属类的一个类方法:code
+ (BOOL)resolveInstanceMethod:(SEL)selector;
复制代码
该方法的参数就是objc_msgSend
没法处理的selector,返回的布尔值表示这个类可否新增一个实例方法处理它。若是这个seletor不是一个实例方法而是一个类方法,那么会有个相似的类方法调用:cdn
+ (BOOL)resolveClassMethod:(SEL)selector;
复制代码
在这个阶段处理未知selector的前提是相关代码已经提早写好了,只等着运行时动态插入类中就能够了。对象
这个方案常常用来实现@dynamic属性
,coreData中的NSManagerObject中的属性就是这么作的。因为CoreData的属性须要从数据库中读取,而后进行动态绑定,而不是经过自动生成的setter和getter去实现。其属性getter和setter实现方法在编译期就已经写好,等到动态方法解析的时候进行setter和getter的方法添加。blog
在这一步中,runtime系统会提供一个方法,让当前接收者返回一个备援接收者来处理未知的selector,这个方法以下:继承
- (id)forwardingTargetForSelector:(SEL)selector;
复制代码
若是当前接收者能找到或者提供这样一个对象,就将其返回,不能就返回nil。
咱们能够利用这一部,来模拟多重继承
的一些特性。好比在一个对象内部,还有其余不少对象,这个对象能够经由这个方法,选择一个处理selector的对象并返回。在外部看来,好像对象是亲自处理该对象的同样。
也能够利用这个过程去完成一些很棒的设计模式,好比装饰器
模式。具体的例子,会在后续文章更新中慢慢补上。
这里要注意的是,在这个过程当中,是没法操做经由这一步转发的消息的。
当前两步都宣告失败以后,runtime系统会建立一个NSInvocation对象,把还没有处理的消息与有关的细节信息所有封装到里面,这个对象包含selector,target以及参数。这个步骤会调用如下方法转发消息:
- (void)forwardInvation:(NSInvocation*)invocation;
复制代码
实现这个方法很简单,只要改变目标,让消息在新目标中调用便可。也能够经过改变参数、更换selector等,变得应用场景更加多变。
这个方法实现时,若是发现不该该由原本调用forwardInvation
方法,就须要调用它父类的同名方法,这样,继承体系中全部的类都有机会处理调用请求,一直到NSObject。若是最后调用了NSObject类的方法,最后会以doesNotRocgnizeSelector
的方式抛出异常。
上面说过,在第二步备援接收者中是没法处理消息的。而在完整的消息转发
中不只可以操做消息,还能轻松拿到消息相关的全部信息。因此,一些看似黑魔法的实现实际上就在完整的消息转发
这个过程当中实现的,好比JSPatch
和Aspects
这两个开源库关键的步骤就是在这个过程当中完成的。