网站后台多个管理员,免费一键生成个人网站,专业微信网站建设多少钱,wordpress微信个人支付Effective Objective-C 2.0 读书笔记—— 消息转发 文章目录 Effective Objective-C 2.0 读书笔记—— 消息转发前言消息转发机制概述动态方法解析处理dynamic的属性用于懒加载 消息转发快速消息转发完整消息转发 总结 前言
在前面我学习了关联对象和objc_msgSend的相关内容dynamic的属性用于懒加载 消息转发快速消息转发完整消息转发 总结 前言
在前面我学习了关联对象和objc_msgSend的相关内容初步了解了OC的动态机制在我们的objc_msgSend的执行操作之中我们提到了如果对象接受了无法解读的消息之后就会进行消息转发。那么什么消息可以被理解呢最基本的就是我们的程序要实现对应的方法由于OC动态语言的特性我们在编译期的时候仍可以在类之中添加方法所以当对象接受到无法解析的消息时就会启动消息转发机制message forwarding。
消息转发机制概述
消息转发一共由两种情况
动态方法解析Dynamic Method Resolution如果一个对象没有实现某个方法Objective-C 会尝试在运行时为该方法动态添加实现。消息转发Message Forwarding如果对象无法处理该消息且无法动态解析方法系统会尝试将该消息转发给其他对象来处理。
动态方法解析
对于动态方法解析来说在这个阶段之中先征询接受者所属的类看其是否能动态的添加方法去处理这个未知的选择子
如果是实例方法未能识别那么首先将调用其所属类的下列类方法:
(BOOL) resolveInstanceMethod: (SEL) selector如果是类方法尚未被实现则调用一下方法
(BOOL) resolveClassMethod: (SEL) selector这两个方法都返回的是Boolean类型表示能否新增这个方法处理这个选择子
处理dynamic的属性
书中用一个被dynamic修饰的属性为例使用这个方法为属性生成setter和getter方法
id autoDictionaryGetter(id self, SEL _cmd) {// 这里可以实现获取字典的逻辑可能是从某个缓存或者实际数据源获取return objc_getAssociatedObject(self, _cmd);
}void autoDictionarySetter(id self, SEL _cmd, id value) {// 这里可以实现设置字典的逻辑可能是更新缓存或者实际数据源objc_setAssociatedObject(self, _cmd, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} (BOOL)resolveInstanceMethod:(SEL)selector {NSString *selectorString NSStringFromSelector(selector);// 检查是否是动态属性的 getter 或 setter 方法if ([selectorString hasPrefix:set]) {// 如果是 setter 方法即 setAutoDictionary:class_addMethod(self, selector, (IMP)autoDictionarySetter, v:); // v: 表示返回 void 类型self 和 _cmd 参数最后是一个 id 类型的参数return YES;}// 如果是 getter 方法即 autoDictionaryif ([selectorString hasPrefix:autoDictionary]) {class_addMethod(self, selector, (IMP)autoDictionaryGetter, :); // : 表示返回 id 类型self 和 _cmd 参数return YES;}// 如果方法不是 setter 或 getter则调用父类的 resolveInstanceMethod:return [super resolveInstanceMethod:selector];
}简单解释一下代码的内容
class_addMethod(self, selector, (IMP)autoDictionarySetter, v:);
class_addMethod 是运行时函数允许你动态地为某个类添加方法。它的参数依次是 self要为哪个类添加方法通常是当前类。selector方法选择器表示要添加的方法的名字。(IMP)autoDictionarySetter方法实现IMP 是指向方法实现的指针这里是 autoDictionarySetter函数的指针。v:方法的签名描述了方法的参数和返回值类型——表示返回 void 类型self 和 _cmd 参数最后是一个 id 类型的参数
IMP是 Implementation Pointer实现指针的缩写是一种在 Objective-C 中表示方法实现的指针类型。具体来说它指向一个实际的函数实现并允许在运行时动态地调用该函数。
IMP 的类型定义如下
typedef id (*IMP)(id, SEL, ...);用于懒加载
相比直接声明并实现方法动态方法解析提供了更多的控制权可以根据需要决定是否加载方法的具体实现。
使用场景一个类可能定义了很多方法但并不是所有方法都会被使用但即使不被使用编译器也会为它分配元数据。通过动态方法解析可以避免为未使用的方法占用内存。方法实现的绑定延迟到实际调用时完成减少类加载和初始化的开销。
#import JCClass.h
#import objc/runtime.h
implementation JCClass(BOOL)resolveInstanceMethod:(SEL)selector {NSString *selectorString NSStringFromSelector(selector);NSLog(enter);// 检查方法选择器是否为 heavyComputation这是我们需要懒加载的方法if ([selectorString isEqualToString:heavyComputation]) {// 使用 class_addMethod 为 heavyComputation 方法动态添加实现class_addMethod(self, selector, (IMP)heavyComputation, :);return YES; // 返回 YES 表示我们已经为该方法添加了实现}return [super resolveInstanceMethod:selector]; // 调用父类的实现
}// 重的计算过程模拟复杂计算
id heavyComputation(id self, SEL _cmd) {NSLog(1);// 模拟一个复杂的计算过程NSLog(Performing heavy computation...);// 假设我们计算结果并缓存它NSString *result This is the result of the heavy computation.;// 将计算结果存储到关联对象中以便下次访问时直接返回objc_setAssociatedObject(self, _cmd, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);return result;
}
endJCClass *obj [[JCClass alloc] init];
NSLog(%,[obj heavyComputation]);
NSLog(%,[obj heavyComputation]);
这个程序运行得到以下结果可以看到当我们调用heavyComputation的第一次就会进入resolveInstanceMethod第二次就会直接调用经过动态绑定的方法 heavyComputation 是一个普通的函数它的存在独立于任何类。通过 class_addMethod它才被绑定为某个类的方法。
这里需要注意我们需要在我们的MyClass类的头文件声明这个heavyComputation的方法让编译器相信这个函数的存在
- (id)heavyComputation;抑或者我们可以不在头文件之中声明直接使用就可以绕过编译器的警告
[obj performSelector:selector(heavyComputation)];当我在学习到这一部分的时候其实是很有疑问的懒加载的目的是不让编译器在编译的过程之中进行对方法进行加载但是C语言写成的函数还是放在了程序之中只是没有绑定对象这样做能节省什么资源呢 在C语言之中函数在程序启动前就已经存在并且占用一定的内存资源但是它的内存分配主要体现在程序的 代码段相比 C 语言函数OC 方法由于其动态的性质内存开销通常更大因为方法不仅包括代码还包括方法名称 (SEL)方法的实现地址 (IMP)方法所属的类元类里存储方法列表其他运行时需要的元信息。 所以我们在编写OC程序的时候在遇到不一定需要的功能时可以避免加载有利于提高程序的使用效率 消息转发
消息转发又被分为两个部分一个是快速消息转发Forwarding to another object另一个是完整消息转发Forwarding the Message
快速消息转发
当我们在动态方法解析没有找到处理选择子的方法时当前对象还有第二次机会对这个选择子的信息进行转发我们就称为快速消息转发
快速消息转发机制通过 forwardingTargetForSelector: 方法将消息转发给另一个对象这个对象会尝试执行该方法。如果目标对象能响应该消息则继续处理。
- (id)forwardingTargetForSelector:(SEL)selector {if (selector selector(foo)) {return someOtherObject; // 将消息转发给 someOtherObject}return [super forwardingTargetForSelector:selector];
}
其中这个someOtherObject是一个实例如果 someOtherObject 能响应 foo 方法则该方法会在 someOtherObject 上执行。
完整消息转发
如果 forwardingTargetForSelector: 返回了 nil 或者目标对象不能处理该消息系统会进入完整的消息转发阶段即通过 methodSignatureForSelector: 和 forwardInvocation: 来处理。
首先创建一个 NSInvocation 对象将与尚未处理的消息相关的所有细节封装在其中。该对象包含以下信息
选择子Selector即方法名称。目标Target接收消息的对象。参数调用方法时传递给方法的参数。
当触发 NSInvocation 对象时消息派发系统message-dispatch system会介入负责将消息转发给目标对象执行相应的方法。
然而这样实现出来的方法与“备援接收者” 方案所实现的方法等效所以很少有人采用这么简单的实现方式。
流程图 总结
若对象无法响应某个选择子则进人消息转发流程。通过运行期的动态方法解析功能我们可以在需要用到某个方法时再将其加入类中。对象可以把其无法解读的某些选择子转交给其他对象来处理。经过上述两步之后如果还是没办法处理选择子那就启动完整的消息转发机制。