当对象接收到无法解读的消息后,就会启动“消息转发”机制。
消息转发分为两大阶段:
第一阶段:征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择器”。这叫做“动态方法解析”。
第二阶段:涉及“完整的消息转发机制”。运行时系统会请求接受者以其他手段来处理与消息相关的方法调用。分两小步:
第一步:请接收者看看有没有其他对象能处理未知消息,若有,则运行时系统会把消息转给那个对象。这叫做“备援接收者”。若没有进行第二步。
第二步:启动完整的消息转发机制,运行时系统会把与消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前的未知消息。
动态方法解析
对象在收到无法解读的消息后,调用其所属类的下列方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
说明:
参数:未知消息
返回类型:Boolean类型,表示这个类是否能新增一个实例方法来处理未知消息。
类在收到无法解读的消息后,调用下列方法:
+ (BOOL)resolveClassMethod:(SEL)sel;
使用这种办法的前提:相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了。
备援接收者
运行时系统请求类,能否将未知消息转给其他接收者来处理:
- (id)forwardingTargetForSelector:(SEL)aSelector;
说明:
参数:未知的选择器。
若当前接收者能找到备援对象,则将其返回,若找不到,返回nil。
在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理未知消息的相关内部对象返回,外界看来,就像是该对象亲自处理这条消息。
注意:若是想在发送备援接收者之前先修改消息内容,那只能通过完整的消息转发机制来做。
完整的消息转发
首先,创建NSInvocation对象,把尚未处理的消息的有关全部细节都封于其中。此对象包含选择器、目标及参数。
在触发NSInvocation对象时,“消息派发系统”把消息派给目标对象。
- (void)forwardInvocation:(NSInvocation *)anInvocation;
说明:
只需改变调用目标,使消息在新目标上得以调用即可。
注意:
在触发消息前,先以某种方式来改变消息内容,比如追加另外一个参数,或是改换选择器等,这是比较有用的实现方法。
如果不改变消息内容,只是使未知消息在新目标上得以调用,那与“备援接收者”方案实现方法等效,使用“备援接收者”方法即可。
实现此方法时,若发现某调用操作不应由本类处理,则需要调用超类的同名方法。这样,集成体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用了NSObject类的方法,那么该方法还会继而调用"doesNotRecognizeSelector:"以抛出异常,此异常表明选择器最终未能得到处理。
消息转发全流程

消息转发,步骤越往后,处理消息的代价就越大,最好在第一步就处理完,这样,运行时系统可以将此方法缓存起来,如果类的实例稍后收到同名选择器,那就无须启动消息转发流程。
如果不修改消息内容,则在第二步进行消息转发即可。
例子
// 头文件
#import <Foundation/Foundation.h>
@interface EOCAutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) id opaqueObject;
@end
// 实现文件
#import "EOCAutoDictionary.h"
#import <objc/runtime.h>
#import "EOCAutoHelper.h"
@interface EOCAutoDictionary()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@property (nonatomic, strong) EOCAutoHelper *heper;
@end
@implementation EOCAutoDictionary
@dynamic string, number, date, opaqueObject;
- (instancetype)init
{
self = [super init];
if (self) {
_backingStore = [NSMutableDictionary new];
_heper = [EOCAutoHelper new];
}
return self;
}
// 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"autoHeplerMethod"]) {
// 备援接受者
return NO;
} else {
// 动态方法解析
if ([selectorString hasPrefix:@"set"]) {
// 向类中动态添加方法
// 参数说明:类,选择器,待添加的函数指针,类型编码(返回值类型@:参数)
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
} else {
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
return NO;
}
// 备援接受者
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSString *selectorString = NSStringFromSelector(aSelector);
if ([selectorString isEqualToString:@"autoHeplerMethod"]) {
// 返回一个内部对象来代替实现方法
return _heper;
}
return [super forwardingTargetForSelector:aSelector];
}
id autoDictionaryGetter(id self, SEL _cmd)
{
EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
NSMutableDictionary *backingStore = typeSelf.backingStore;
NSString *key = NSStringFromSelector(_cmd);
return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self, SEL _cmd, id value)
{
EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
NSMutableDictionary *backingStore = typeSelf.backingStore;
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
// 删除':'
[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];
}
}
@end
// 头文件
#import <Foundation/Foundation.h>
@interface EOCAutoHelper : NSObject
- (void)autoHeplerMethod;
@end
// 实现文件
#import "EOCAutoHelper.h"
@implementation EOCAutoHelper
- (void)autoHeplerMethod
{
NSLog(@"EOCAutoHelper");
}
@end
// 使用
EOCAutoDictionary *dict = [EOCAutoDictionary new];
// 测试动态方法解析
dict.date = [NSDate dateWithTimeIntervalSince1970:475372800];
NSLog(@"dict.date : %@",dict.date);
// 测试备援接收者
[dict performSelector:@selector(autoHeplerMethod)];
网友评论