首先要搞清楚一点的是,在OC中方法的调用是向对象发送消息。
比如:[person run],就是向person这个对象发送叫做run的消息。因为Objective-C运行时的存在,即使person对应的类中没有run这个方法,在编译期间也不会报错,但程序最终会报错:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person run]: unrecognized selector sent to instance
本文就从这个异常说起,彻底搞清楚Objective-C的消息转发机制。
当向一个对象发送某个消息时,程序在运行期间会通过对象所属类的方法列表中去找对应的方法,找到就执行,找不到就从父类中找。如果最后在NSObject中也没有这个方法时,运行时系统会提供3套备选方案进行补救。
第一种方案:
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel
如果是实例方法,则需要实现resolveInstanceMethod:(SEL)sel方法,如果是类方法,实现resolveClassMethod:(SEL)sel即可。
比如上面Person类中的实例方法run,如果没有实现这个方法,就可以通过实现resolveInstanceMethod:(SEL)sel来进行补救。
在Person.m文件中添加方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel==@selector(run)){
class_addMethod(self,sel,(IMP)runMethod,"v@:");
return YES;
}
return [super resolveInstanceMethod :sel];
}
void runMethod(id self,SEL _cmd){
NSLog(@"重新实现run方法");
}
一句话总结这种方案就是:给目标对象动态添加一个方法实现。
第二种方案:
实现forwardingTargetForSelector方法。
还是上面的例子,新建一个类Tiger,在这个类中实现方法run如下:
-(void) run{
NSLog("tiger run ");
}
在Person.m文件中添加方法:
-(id)forwardingTargetForSelector:(SEL)sel{
return [ [Tiger alloc]init];
}
一句话总结这种方案,本身类没有实现的方法,可以转给其他类实现。
第三种方案:
实现methodSignatureForSelector和forwardInvocation两个方法,第一个方法用于生成签名,第二个方法实际作为转发对象调用方法。
-(void)forwardInvocation:(NSInvocation*)invocation{
SEL selector=[invocation selector];
Tiger *tiger=[[Tiger alloc]init];
if(tiger respondsToSelector:selector){
[invocation invokeWithTarget:tiger];
}
}
这种方案本质上和第二种方案一样,转发给其他类进行实现。