《Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法》第2章对象、消息、运行期,本章讲解运行期环境中各个部分协同工作的原理之后,你的开发水平将会进一步提升。本节为大家介绍消息转发机制。
第12条:理解消息转发机制(3)
本例的关键在于resolveInstanceMethod:方法的实现代码:
- + (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实例上访问某个属性时,运行期系统还找不到对应的选择子,因为所需的选择子既没有直接实现,也没有合成出来。现在假设要写入opaqueObject属性,那么系统就会以“setOpaqueObject:”为选择子来调用上面这个方法。同理,在读取该属性时,系统也会调用上述方法,只不过传入的选择子是opaqueObject。resolveInstanceMethod方法会判断选择子的前缀是否为set,以此分辨其是set选择子还是get选择子。在这两种情况下,都要向类中新增一个处理该选择子所用的方法,这两个方法分别以autoDictionarySetter及autoDictionaryGetter函数指针的形式出现。此时就用到了class_addMethod方法,它可以向类中动态地添加方法,用以处理给定的选择子。第三个参数为函数指针,指向待添加的方法。而最后一个参数则表示待添加方法的“类型编码”(type encoding)。在本例中,编码开头的字符表示方法的返回值类型,后续字符则表示其所接受的各个参数。
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;
- /** The selector will be for example, "setOpaqueObject:".
- * We need to remove the "set", ":" and lowercase the first
- * letter of the remainder.
- */
- NSString *selectorString = NSStringFromSelector(_cmd);
- NSMutableString *key = [selectorString mutableCopy];
- // Remove the ':' at the end
- [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
- // Remove the 'set' prefix
- [key deleteCharactersInRange:NSMakeRange(0, 3)];
- // Lowercase the first character
- NSString *lowercaseFirstChar =
- [[key substringToIndex:1] lowercaseString];
- [key replaceCharactersInRange:NSMakeRange(0, 1)
- withString:lowercaseFirstChar];
- if (value) {
- [backingStore setObject:value forKey:key];
- } else {
- [backingStore removeObjectForKey:key];
- }
- }
EOCAutoDictionary的用法很简单:
- EOCAutoDictionary *dict = [EOCAutoDictionarynew];
- dict.date = [NSDatedateWithTimeIntervalSince1970:475372800];
- NSLog(@"dict.date = %@", dict.date);
- // Output: dict.date = 1985-01-24 00:00:00 +0000
其他属性的访问方式与date类似,要想添加新属性,只需用@property来定义,并将其声明为@dynamic即可。在iOS的CoreAnimation框架中,CALayer类就用了与本例相似的实现方式,这使得CALayer成为“兼容于键值编码的”(key-value-coding-compliant)容器类,也就等于说,能够向里面随意添加属性,然后以键值对的形式来访问。于是,开发者就可以向其中新增自定义的属性了,这些属性值的存储工作由基类直接负责,我们只需在CALayer的子类中定义新属性即可。
要点
若对象无法响应某个选择子,则进入消息转发流程。
通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
对象可以把其无法解读的某些选择子转交给其他对象来处理。
经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。