1. 若对象无法相应某个选择子,则进入消息转发流程。
2. 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
3. 对象可以把其无法解读的某些选择子转交给其他对象来处理。
4. 经上述两步,如果还是没办法处理选择子,就启动完整的消息转发机制。
5. 消息转发分为两个大部分。
第一阶段征询接收者所属的类,看其能否动态添加方法,以处理当前那个“位置的选择子”(unknown selector),这叫做“动态方法解析”(dynamic method resolution)。
第二阶段,若有备援的接收者,则传递给那个对象,否则启动完整的消息转发机制。
运行期系统会把与消息有关的全部细节都封装到NSInvocation对象里,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。
6. 动态方法解析。
对象在接受到无法解读的消息后,首先将调用其所属类的下列类方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);
+ (BOOL)resolveInstanceMethod:(SEL)selector
{
NSString *selectorString = NSStringFromSelector(selector);
if (/* selector is from a @dynamic property */)
{
if([selectorString hasPrefix:@“set”])
{
class_addMethod(self, selector, (IMP)autoDictionarySetter, “v@:@“);
}else {
class_addMethod(self, selector, (IMP)autoDictionaryGetter, “@@:“);
}
return YES;
}
return [super resolveInstanceMethod:selector];
}
7. 备援接收者
- (id)forwardingTargetForSelector:(SEL)selector
注:我们无法操作经由这一步所转发的消息,若是想再发送给备援接收者之前修改消息内容,那就要通过完整的消息转发机制来做了。
8. 完整的消息转发。
首先创建NSInvocation对象,将尚未处理的那条消息有关的全部细节都封装其中。
此对象包括选择子、目标(targer)及参数。
再触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。
- (void)forwardInvocation:(NSInvocation*)invocation;
实现此方法时,若发现某调用操作不应由本类处理,则需调用超类的同名方法。
这样继承体系中的每个类都有机会处理此调用请求,直至NSObject。
如果最后调用了NSObject类的方法,那么该方法还会继而调用“doesNotRecognizeSelector”,以抛出异常。
此异常表明选择子最终未能得到处理。
9. 以完整的例子演示动态方法解析
设计思路:由开发者来添加属性定义,并将其声明为@dynamic,而类则会自动处理相关属性值的存放与获取操作。
@interface EOCAutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) id opaqueObject;
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end
@interface EOCAutoDictionary
@dynamic string, number, date, opaqueObject;
- (id)init {
if((self = [super init])){
_backingStore = [NSMutableDictionary new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)selector
{
NSString *selectorString = NSStringFromSelector(selector);
if([selectorString hasPrefix:@“set”])
{
class_addMethod(self, selector, (IMP)autoDictionarySetter, “v@:@“);
}else {
class_addMethod(self, selector, (IMP)autoDictionaryGetter, “@@:“);
}
return YES;
}
@end
当开发者首次在EOCAutoDictionary实例上访问某个属性时,运行期系统还找不到对应的选择子,因为所需的选择子既没有直接实现,也没有合成出来。所以会调用resolveInstanceMethod:继而调用class_addMethod方法。
getter 函数可以用下列代码实现:
id autoDictionaryGetter (id self, SEL _cmd)
{
// Get the backing store from the object
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
// The key is simply the selector name
NSString *key = NSStringFromSelector(_cmd);
// Return the value
return [backingStore objectForKey:key];
}
而 setter 函数可以这么写:
void autoDictionarySetter(id self,SEL _cmd, id value)
{
// Get the backing store from the object;
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
// for “ :setOpaqueObject:” tobe “opaqueObject”
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
[key deleteCharactersInRange:NSMakeRange(0, 3)];
NSString 8lowercaseFistChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0,1) withString:lowercaseFirstChar];
if(value)
[backingStore setObject:value forKey:key];
else
[backingStore removeObjectForKey:key];
}
iOS 的 CALayer 类似本例的实现方式。使得CALayer成为“兼容于键值编码的”容器类(key-value-coding-compliant);