iOS消息转发机制和使用
消息转发机制的回顾以及涉及的几个方法的备忘
一、OC消息发送原理 + 消息转发机制
1、由于OC的动态特性,只有当程序运行起来之后,才知道要真正执行哪个函数(动态绑定)。在编译过程向类发送了其无法理解的消息并不会报错,因为在运行时,我们可以改变对象调用的方法、向类中添加方法。
2、OC消息发送原理、方法查找过程
(1)调用一个方法(包括respondsToSelector),编译器将OC代码,转换成C函数,给对象发送消息 : void objc_msgSend(id self, SEL cmd,...) ,第一个参数是接收者,第二个参数是方法(名),后面是消息的参数。
(2)objc_msgSend查找方法的过程:
- 实例对象根据其isa指针,找到其所属的class,然后遍历其methodLists,如果找到则根据IMP函数指针去调用,并且缓存(objc_cache);如果没有找到,那么根据这个类的super_class找到其父类,再看其父类是否能相应这个方法就可以了,直到super_class为nil时,就无法响应这个方法了,此时就触发消息转发机制。
- 当使用类名调用类方法(+方法)时,只需要根据class的isa指针,找到其meta-class,然后通过meta-class的methodLists找到相应的方法既可(“类”是“元类”的对象)。
3、如果对象接收到无法解读的消息后(未查询到该方法),就会启动“消息转发”机制,我们可在此过程告诉对象应该如何处理未知消息。如果我们不做任何处理,或处理无效,则会调用doesNotRecognizeSelector:,造成异常崩溃:unrecognized selector sent to instance 0xxx
二、消息转发机制的处理过程
消息转发机制依次的三个过程:
1、动态方法解析
第一阶段,先征询接收者所属的类,是否需要动态的添加方法,用来处理当前未找到的方法。对象在无法解读消息时会首先调用所属类的下列类方法,来判断是否能接收消息:
- + (BOOL) resolveInstanceMethod:(SEL)selector,参数为那个未知的选择子,返回值表示这个类能否新增一个实例方法处理此选择子。
- 如果是类方法 ,则调用 + (BOOL) resolveClassMethod:(SEL)selector,有一点要注意,类方法的添加需要在其“元类”里面。
举例:
//消息转发机制的第一步 :动态方法解析 + (BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selName = NSStringFromSelector(sel); if ([selName hasPrefix:@"doSomeThing"]) {//判断特定无法响应的方法 class_addMethod(self, sel, (IMP)otherOneDoSomeThing, "v@:");//动态添加响应方法 return YES; } return [super resolveInstanceMethod:sel]; } //动态将实现转到这个函数(或者就是单纯的添加doSomeThing方法) void otherOneDoSomeThing(id self ,SEL _cmd){ NSLog(@"class:%@, sel:%s",self,sel_getName(_cmd)); NSLog(@"原对象无法响应该消息,在动态方法解析时添加了一个方法来处理该消息"); }
2、备用的接收者
第二阶段,如果动态方法解析没有发现添加的方法,那么尝试转发给其他对象来处理这个方法。该步骤调用的方法是:
- - (id) forwardingTargetForSelector:(SEL)selector
举例:
- (id)forwardingTargetForSelector:(SEL)aSelector{ NSString * selString = NSStringFromSelector(aSelector); if([@"doSomeThing" isEqualToString:selString]){ OtherObject *someone = [[OtherObject alloc] init];//备选对象 if ([someone respondsToSelector:aSelector]) { return someone;//如果可以响应该方法,则直接转交新对象处理 } } return [super forwardingTargetForSelector:aSelector];//如果无合适的备选对象,则继续转发 }
3、完整的消息转发机制
第三阶段,如果没有可用的备选者,那么系统就会把消息所有相关内容封装成一个NSInvocation对象,再做最后的尝试,启动完整的消息转发。先调用methodSignatureForSelector:获取方法签名,然后再调用forwardInvocation:进行处理,这一步的处理可以直接转发给其它对象,即和第二步的效果等效,但是很少有人这么干,因为消息处理越靠后,就表示处理消息的成本越大,性能的开销就越大。所以,在这种方式下,一般会改变消息内容,比如增加参数,改变选择子等等,具体根据实际情况而定。
- - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- - (void)forwardInvocation:(NSInvocation *)anInvocation
举例
//获取方法签名 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSString *method = NSStringFromSelector(aSelector); if ([@"doSomeThing" isEqualToString:method]) { /* 手动创建签名 写法例子一 v@:@ 字符说明:(1)v:返回值类型void;(2)@:id类型,执行sel的对象;(3): SEL;(4)@:参数 写法例子二 @@: 字符说明:(1)@:返回值类型id;(2)@:id类型,执行sel的对象;(3):SEL */ NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"]; return signature; } return nil; } - (void)forwardInvocation:(NSInvocation *)anInvocation { //*----------- 处理方式一:不改变sel -------------*/ // 拿到这个消息 SEL selector = [anInvocation selector]; // 转发消息 AnotherObject *otherObject = [[AnotherObject alloc] init]; if ([otherObject respondsToSelector:selector]) { // 调用这个对象,进行转发 [anInvocation invokeWithTarget:otherObject]; } else { [super forwardInvocation:anInvocation]; } //*---------------------------------------------*/ //*----------- 处理方式二:改变sel -------------*/ SEL selector = @selector(myAnotherMethod:); NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"]; anInvocation = [NSInvocation invocationWithMethodSignature:signature]; [anInvocation setTarget:self]; [anInvocation setSelector:@selector(myAnotherMethod:)]; NSString *param = @"参数"; // 消息的第一个参数是self,第二个参数是选择子,所以"参数"是第三个参数 [anInvocation setArgument:¶m atIndex:2]; if ([self respondsToSelector:selector]) {//如果自己响应,就自己处理 [anInvocation invokeWithTarget:self]; return; } else { AnotherObject * otherObject = [[AnotherObject alloc] init]; if ([otherObject respondsToSelector:selector]) {//交给另外的对象来处理 [anInvocation invokeWithTarget:otherObject]; return; } } [super forwardInvocation:anInvocation]; //*---------------------------------------------*/ } //类中的另一个方法,来处理消息 - (void)myAnotherMethod:(NSString*)para { NSLog(@"交给我自己的另一个方法来处理:%@", para); }
三、应用场景
1、 为@dynamic实现方法
2、间接实现多继承
- JSPatch,通过消息转发机制来进行JS和OC的交互,从而实现iOS的热更新。
- 虽然苹果大力整改热更新让JSPatch的审核通过率在有一段时间里面无法过审,但是后面bang神对源码进行代码混淆之后,基本上是可以过审了。
- 下面截图只摘出来用到消息转发的部分:关键点就是在第三阶段,通过invocation拿到方法参数,然后传给JS,调用JS的实现函数。
四、总结
1、消息转发机制的时机图示
2、简单理解
(1)首先,若对象无法响应某个方法调用,则进入消息转发流程。
(2)开始第一步,通过运行时的动态方法解析,可以将需要的某个方法,加入到类中。
(3)上一步失败,开始第二步,将消息转发给其他对象处理。
(4)上述两步失败,启动完整的消息转发机制,通过封装NSInvocation,明确指出方法的响应者(甚至改变SEL)。
(5)上述都失败,抛出异常。