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);
Effective Objective-C 2.0 编写高质量iOS与OS X代码 理解消息转发机制,布布扣,bubuko.com