苹果对于消息的解释是: message aren’t bound to method implementations until Runtime; 直到运行时消息才会与方法实现进行绑定. 在进行消息转发这个我看了很久才懂了一点的东西之前,先解释一些东西.
消息: OC中调用方法就是向对象发送消息 其实这个层面上是很好理解的 场景: 初期书写代码时候常常会因为调用一个没有实现的方法导致crash为什么?消息发送的步骤是啥?如果找不到咋办,怎么防止崩溃,消息转发到底是个啥?
举个栗子 [receiverObject message] 一个对象 receiverObject 一个方法message 根据苹果对于消息的解释,在运行时与方法实现绑定变成了objc_msgSend(receiverObject , selector),因此你看到了一个方法objc_msgSend
objc_msgSend方法是从不返回数据的,是方法在运行时实现被调用了之后才返回的objc_msgSend,除了这个方法,在消息传递中编辑器会根据情况在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, objc_msgSendSuper_stret这四个方法中选择一个调用,选择的依据是:如果消息传递给父类就用带有Super的,如果消息返回的数据结果不是简单值而是数据结构的时候会调用带有stret的解释一下objc_msgSend这个方法 其中有两个参数一个id,一个SEL 还有其他参数. id和SEL是两个隐藏参数,一个是接收消息的对象self,一个是方法选择器当前方法的SEL指针.其他参数即是消息中的所有参数.而self 和SEL _cmd 是在代码被编译的时候被插入到方法实现中的
总结:objc_msgSend中有原函数中的所有参数,还有两个隐藏参数self 和SEL,而且他不返回数据,在实现调用之后才返回,并且还有三个 类似的方法用在不同场景下消息发送的步骤
检查selector是否可以忽略开始查找该类实现的IMP如果找不到就进入动态解析如果动态解析不处理返回NO,可以先进性重定向(nil 或者self 进入转发)进入消息转发任一步骤能够找到实现都会结束,不再进行下边的步骤,接下来,按照我浅显的理解进行一下说明
检查selector是否可以忽略 为啥selector可以忽略?例如:如果对象为nil (ObjC是允许对 nil对象执行任何方法而不crash) 则运行时该方法就会护绿,再例如:Mac OS X开发有了垃圾回收则不理会retain和release函数
开始查找该类实现的IMP 1.selector获取方法地址(获取选择器对应的IMP) NSObjectde 一个实例方法可以获取方法地址 methodForSelector 不是OC特性,是Runtime提供的
void(* selector)(id, SEL, BOOL);2.如何查找IMP? 首先我们先去Cache中查找,如果找不到IMP则去类的方法列表中遍历查找,如果找不到去父类找,一直找到NSObject类,如果还找不到就动态解析(上一篇中貌似说过,就这样赘述了,哦哈哈哈)
如果找不到就进入动态解析 动态方法解析:这个我理解的有些混乱先说几个我确定的东西
动态方法解析执行于消息转发机制浸入前;动态方法解析会优先给你机会让你得到IMP,就是说你可以动态给他一个实现的代码;如果你想让他进入转发则需要返回NO前边说了找不到方法,要进入动态解析了,那么Runtime系统就会调用给一个函数 resolveInstanceMethod:或者 resolveClassMethod(分别用来添加实例方法实现和类方法实现) 来给我们一个机会来添加方法的实现,需要的函数为class_addMethod举个栗子
//定义一个方法 -(void)dynamicAddMethodIMP(id self,SEL _cmd){ ...//方法实现的代码 } @implementation Class //给resolveThisMethodDynamically函数提供函数实现为dynamicAddMethodIMP中的代码 +(BOOL)resolveInstanceMethod:(SEL)aSEL{ if(aSEL == @selector(resolveThisMethodDynamically)){ class_addMethod([self class], aSEL, (IMP)dynamicAddMethodIMP,"v@:");//"v@:"代表返回值和参数 return YES; } return [super resolveInstanceMethod:aSEL]; } ps:@dynamic修饰的属性如果动态解析不处理返回NO,可以先进性重定向
消息转发前,Runtime允许我们替换掉消息的接受者为其他对象
-(id)forwardingTargetForSelector:(SEL)aSelector{ if(aSelector == @selector(myMethod:)){ return alternateObjc;//向返回的对象重新发送消息 } return [super forwardingTargetForSelector:aSelector]; }//返回self或者nil 进入消息转发机制进入消息转发 在动态解析不作处理返回NO时,触发了消息转发机制forwardInvocation:方法执行
forwardInvocation:其实意义上是一个不能识别消息的分发中心,将这些不能识别的消息转发给不同的接收对象,或者转发给同一个对象,再或者将消息翻译成另外的消息或者简单地吃掉,因此没有响应也不会报错,这一切取决于方法的具体实现, 每个对象都继承了这一方法,但是NSObject里边只是简单地调用了doesNotRecognizeSelector:
-(void)forwardInvocation:(NSInvocation *)anInvocation{ if([someOtherObjc respondsToSelector:[anInvocation selector]]){ [anInvocation invokeWithTarget:someOtherObjc]; }else [super forwardInvocation]; }//只会在消息接收对象无法正常响应消息时才会被调用 ps:NSInvocation 类型,在forwardInvocation:消息发送前,Runtime系统会向对象发送methodSignatureForSelector:消息,并且返回的 方法签名用于生产NSInvocation对象,所以重写forwardInvocation:“` -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSMethodSignature *signature = [super methodSignatureForSelector:selector]; if(!signature){ signature = [surrogate methodSignatureForSelector:selector]; } return signature; }//返回方法签名生成NSInvocation,所以每次重写forwardInvocation也要重写该方法
如果一个对象想要转发它接收的任何远程消息,它得给出一个方法标签来返回准确的方法描述methodSignatureForSelector:,这个方法最终响应 被转发的消息,从而生成NSInvocation对象来描述消息和消息参数,这个方法最终响应被转发的消息.NSInvocation封装了原始的消息和消息的参数,我们可以通过forwardInvocation:方法来对不能处理的消息作处理,也可以转发给其他对象处理而不抛出错误
PS:对于重定向和转发的区别: 我个人理解啊,没有找到可能也是没有用心找资料; 重定向:将消息重新转发给其他新的对象重新发送消息; 转发:不区分消息,一个小心分发的中心,对不能处理的消息进行统一的或者不统一的对象,亦可以变成别的消息,也可以不处理.