若想令类能理解某条消息,我们必须实现对应的方法才行,但是在编译期向类发送了其无法理解的消息并不会报错,因为在运行期可以继续向类中添加方法,所以编译器在编译时还不确定类中到底会不会有某个方法的实现。当对象接收到无法解读的消息后,就会启动“消息转发”机制,程序可由此过程告诉对象应该如何处理未知消息。
消息转发分为两大阶段,第一阶段先征询接收者所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择子”,这叫做“动态方法解析”。如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。此时运行期系统会请求接收者以其他手段来处理与消息相关的方法调用。这又细分为两小步。首先请接收者看看有没有其他对象可以处理这条消息,若有,则运行期会把消息转给那个对象,于是消息转发过程结束,一切如常。若没有“备援的接收者”,则启动完整的消息转发机制,运行期会把与消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。
1.动态方法解析
对象在无法解读消息会首先调用所属类的下列类方法:
+ (BOOL) resolveInstanceMethod:(SEL)selector
参数为那个未知的选择子,返回值表示这个类能否新增一个实例方法处理此选择子。假如尚未实现的方法不是实例方法而是类方法则运行期会调用另一个方法:+ (BOOL) resolveClassMethod:(SEL)selector。使用这种方法的前提是:相关方法的实现代码已经写好,只等着运行的时候动态插入到类里面就可以了。此方案常用来实现@dynamic属性。
2.备援接收者
当前接收者还有第二次机会能处理未知的选择子,这一步中,运行期会问它:能不能把这条消息转发给其他接收者来处理。与该步骤对应的处理方法:
- (id) forwardingTargetForSelector:(SEL)selector
方法参数代表未知的选择子,若当前接收者能找到备援对象,则将其返回,若找不到就返回nil。通过此方案我们可以用“组合”来模拟出“多重继承”的某些特性(因为OC属于单继承,一个字类只能继承一个基类)。在一个对象内部,可能还有一系列其他对象,该对象可能由此方法将能够处理某选择子的相关内部对象返回,这样的话,在外界看来,好像该对象亲自处理了这些消息。
3.完整的消息转发
如果转发算法已经到了这一步的话,那么唯一能做的就是启用完整的消息转发机制了。首先创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封装与其中。此对象包含选择子、目标(target)、参数。在出发NSInvocation对象时“消息派发系统”将亲自出马,把消息指派给目标对象。
- (void) forwardInvocation:(NSInvocation *)invocation
这个方法可以实现很简单:只需改变调用目标,使消息在新目标上得以调用即可,然而这样实现出来的方法与“备援接收者”方案所实现的方法等效,所以很少有人采用这么简单的实现方式。比较有用的实现方式为:在出发消息前,现已某种方式改变消息内容,比如追加另外一个参数,或者改换选择子等等。
实现了此方法若发现某调用操作不应由本类处理,则需调用超类的同名方法。这样的话继承体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用了NSObject类的方法,那么该方法还会继续调用“doesNotRecognizeSelector:”以抛出异常,此异常表明选择子最终未能得道处理。
例子1:
@interface WKAutoDictionary : NSObject @property (nonatomic, copy) NSString *string; @property (nonatomic, strong) NSNumber *number; @property (nonatomic, strong) NSDate *date; @property (nonatomic, strong) id opaqueObject; @end
#import "WKAutoDictionary.h" #import <objc/runtime.h> @interface WKAutoDictionary () @property (nonatomic, strong) NSMutableDictionary *backingStore; @end @implementation WKAutoDictionary @dynamic string, number, date, opaqueObject; - (instancetype)init { if (self = [super init]) { _backingStore = [NSMutableDictionary dictionary]; } return self; } /* * 找不到sel首先会进入 */ + (BOOL)resolveInstanceMethod:(SEL)sel { NSString *selString = NSStringFromSelector(sel); if ([selString hasPrefix:@"set"]) {//说明调用的时set方法 class_addMethod(self, //class_addMethod 可以向类中动态添加方法 sel, (IMP)autoDictionarySetter,//函数指针类型,指向待添加的方法 "v@:@");// 表示待添加方法的“类型编码” }else { class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:"); } return YES; } void autoDictionarySetter (id self, SEL _cmd, id value) { //获取当前self的字典 WKAutoDictionary *typedSelf = (WKAutoDictionary *)self; NSMutableDictionary *backingStore = typedSelf.backingStore; NSString *selString = NSStringFromSelector(_cmd); NSMutableString *key = [selString mutableCopy]; //删除set方法的“:” [key deleteCharactersInRange:NSMakeRange(key.length-1, 1)]; //删除 “set” [key deleteCharactersInRange:NSMakeRange(0, 3)]; //让第一个字符小写 NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString]; [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar]; if (value) { [backingStore setObject:value forKey:key]; }else { [backingStore removeObjectForKey:key]; } } id autoDictionaryGetter (id self, SEL _cmd){ //获取当前self的字典 WKAutoDictionary *typedSelf = (WKAutoDictionary *)self; NSMutableDictionary *backingStore = typedSelf.backingStore; NSString *key = NSStringFromSelector(_cmd); return [backingStore objectForKey:key]; }
例子2:
#import "Developer.h" #import "Finance.h" #import <objc/runtime.h> @implementation Developer - (void)doDeveloper { NSLog(@"Developer doWork!"); } + (BOOL)resolveInstanceMethod:(SEL)sel { /* 如果当前对象调用了一个不存在的方法 Runtime会调用resolveInstanceMethod:来进行动态方法解析 我们需要用class_addMethod函数完成向特定类添加特定方法实现的操作 返回NO,则进入下一步forwardingTargetForSelector: */ /* class_addMethod(self, sel, class_getMethodImplementation(self, sel_registerName("doDeveloper")), "v@:"); return [super resolveInstanceMethod:sel]; */ return NO; } - (id)forwardingTargetForSelector:(SEL)aSelector { /* 在消息转发机制执行前,Runtime 系统会再给我们一次重定向的机会 通过重载forwardingTargetForSelector:方法来替换消息的接受者为其他对象 返回nil则进步下一步forwardInvocation: */ /* return [[Finance alloc] init]; */ return nil; } -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ /* 获取方法签名进入下一步,进行消息转发 */ return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { /* 消息转发 */ return [anInvocation invokeWithTarget:[[Finance alloc] init]]; }
总结:
1.若对象无法响应某个选择子,则进入消息转发流程。
2.通过运行期的动态方法解析功能,我们可以在需要某个方法时再将其加入类中。
3.对象可以把其无法解读的某些选择子转交给其他对象来处理。
4.经过上述两步后,如果还是没办法处理选择子,那就启动完整的消息转发机制。