ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JRSwizzle源码逐行解读:method_exchangeImplementations与方法交换全机制解析

JRSwizzle源码逐行解读:method_exchangeImplementations与方法交换全机制解析 JRSwizzle源码逐行解读method_exchangeImplementations与方法交换全机制解析【免费下载链接】jrswizzleone-stop-shop for all your method swizzling needs项目地址: https://gitcode.com/gh_mirrors/jr/jrswizzleJRSwizzle 是 Objective-C 开发中处理**方法交换Method Swizzling**的一站式开源库核心代码仅两个文件、不到 200 行。本文将逐行解读它的源码带你彻底搞懂method_exchangeImplementations的工作原理、继承场景下的方法提升技巧以及它与class_addMethod的经典配合机制——即使你是运行时新手也能轻松读懂。一、方法交换解决什么问题在 Objective-C 中[obj doSomething]这行消息发送的本质是在类的方法列表里根据选择器SEL找到对应方法的实现函数指针IMP然后跳转执行。方法交换就是在运行时把两个方法的 IMP 对调。交换之后方法名和签名不变但实际执行的是对方的代码。常见用途给第三方库的方法无侵入加日志、埋点A/B 测试中临时替换业务逻辑监控或拦截系统框架的调用但方法交换有一个新手最容易踩的坑继承。如果子类的某个方法是从父类继承来的直接对父类方法动手会把整条继承链全部改坏。JRSwizzle 存在的最大意义就是在任意系统版本上正确处理这个继承问题。二、项目结构一览核心代码就两个文件 文件作用JRSwizzle.h公开 API 声明只有 4 个类方法JRSwizzle.m全部核心实现约 166 行JRSwizzleTest/JRSwizzleTest.m继承/直接实现的正确性测试JRSwizzleTest/MethodSwizzle.m早期 Ballard 实现的对照参考JRSwizzle.podspecCocoaPods 描述文件iOS 4.3 / macOS 10.6整个库不依赖任何第三方只依赖Foundation和 ObjC 运行时头文件。三、四种交换 API 速览 ⚡JRSwizzle.h 中通过NSObject的类别暴露了 4 个 APIAPI用途jr_swizzleMethod:withMethod:error:交换两个实例方法最常用jr_swizzleClassMethod:withClassMethod:error:交换两个类方法jr_swizzleMethod:withBlock:error:用Block 注入替换实例方法jr_swizzleClassMethod:withBlock:error:用 Block 注入替换类方法最基础的使用方式一行搞定[SomeClass jr_swizzleMethod:selector(foo) withMethod:selector(my_foo) error:error];所有 API 都返回NSError**出参找不到方法时会写入高质量诊断信息这是它在鲁棒性上优于早期实现的体现。四、核心源码逐行解读jr_swizzleMethod 三步走 下面是对 JRSwizzle.m 中jr_swizzleMethod:withMethod:error:的逐步拆解现代运行时路径即OBJC_API_VERSION 2。第 0 步兼容性预处理文件开头有两处为兼容老系统服务的定义JRSwizzle.miOS 与 macOS 需要的运行时头文件不同objc/runtime.hvsobjc/objc-class.hJRSwizzle.m老运行时没有object_getClass用obj-isa兜底取类。这正是 README 所说在 Mac OS X v10.3 到 iOS 2.0 全部版本都能工作的原因。第 1 步查找两个方法L34–L52用class_getInstanceMethod分别取出原始方法和替身方法的Method结构体。任何一个查不到就通过 SetNSError 宏 写入带函数名和描述的错误信息并返回NO——先校验、后动手保证不会留下改了一半的脏状态。第 2 步关键技巧——把方法提升到目标类L54–L61⭐这是整个库最精髓的地方源码只有两次class_addMethodclass_addMethod(self, origSel_, class_getMethodImplementation(self, origSel_), method_getTypeEncoding(origMethod)); class_addMethod(self, altSel_, class_getMethodImplementation(self, altSel_), method_getTypeEncoding(altMethod));class_addMethod的特性是类上如果已有该方法什么都不做没有才添加。所以这两行代码的实际效果是方法本来就是目标类自己定义的→ 无操作直接通过方法是从父类继承来的 → 把它复制一份连同 IMP 和类型编码挂到目标类上变成类自己的方法。这一步叫方法提升hoisting。它保证了第 3 步交换的永远是目标类自己的两份方法记录绝不会误伤父类和其它兄弟子类。第 3 步交换实现L63method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));到这里两个方法已确定都住在同一个类的方法列表里可以放心交给method_exchangeImplementations完成对调返回YES。五、method_exchangeImplementations 底层机制剖析 这个运行时 API 的语义非常纯粹把两个Method记录里的method_imp字段互换仅此而已。它不修改方法名、不改类型编码、不动方法列表结构。理解它有三个要点前提是同一类——两个方法必须位于同一个类或元类的方法列表中否则行为未定义。这就是为什么 JRSwizzle 要先做第 2 步的提升交换的是实现不是名字——交换后[obj foo]依然按foo的签名去调用只是执行到了my_foo的函数体所以两边签名不一致时仍有风险它是原子级的安全原语——只做一次指针互换比手动改写method_imp少出错这也是 macOS 10.5 / iOS 2.0 之后官方推荐的做法。六、旧版兼容路径手动交换的 Ballard 实现 ️对于OBJC_API_VERSION 2的老系统没有method_exchangeImplementationsJRSwizzle.m 走一条完全手工的路径致敬的是 Kevin Ballard 的经典实现遍历方法列表L70–L84用class_nextMethodList只找目标类直接拥有的方法刻意排除继承方法提升继承方法L112–L127缺哪个就用class_getInstanceMethod从继承链取出手工拼一个objc_method_list再class_addMethods挂上去——注释里还特意把obsolete字段置空来安抚 valgrind手动交换L130–L132IMP temp directOriginalMethod-method_imp; directOriginalMethod-method_imp directAlternateMethod-method_imp; directAlternateMethod-method_imp temp;三步逻辑与现代路径完全同构先提升再交换。测试目录里的 MethodSwizzle.m 保留了这一实现的完整对照版本可结合阅读。七、类方法与 Block 注入进阶 API 类方法交换只有一行JRSwizzle.mreturn [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_];原理一句话类方法是元类metaclass的实例方法。GetClass((id)self)取出元类后类方法交换就退化成了普通的实例方法交换。Block 注入JRSwizzle.m则更巧妙分四步imp_implementationWithBlock把 Block 包装成一个 IMP用方法名 Block 地址拼出唯一选择器如_jr_block_foo_0x1005避免重名class_addMethod把 Block 方法挂上类再走标准流程与原方法交换返回一个NSInvocation在 Block 内部可以调它来执行原始实现——实现执行我的逻辑后再补刀原方法的经典模式。注意 JRSwizzle.h 的注释示例Block 里通过__block NSInvocation *invocation引用自身先打日志、再invoke原方法、最后取返回值。版本历史显示该 API 是 v1.1.0 加入的作者也坦承NSInvocation不是最快的路径性能敏感场景请谨慎。八、测试用例如何验证继承也正确 ✅JRSwizzleTest.m 里两个场景测试恰好对应 README 对比表中的第 7、8 行其余实现各有一行是 NO场景 7直接实现父类A7定义foo7子类B7重写了它类别里加了altFoo7。对B7交换后——b foo7走altFoo7而父类实例a foo7完全不受影响L41-L78场景 8继承实现B8没有重写foo8是从父类继承的。交换后B8走altFoo8A8依然走原foo8L114-L151。第二个场景就是提升技巧的用武之地foo8本来在A8的方法列表里先被复制到B8再交换所以父类安然无恙。两个测试断言里那句// CORRECT BEHAVIOR注释正是整个库的立身之本。九、上手指南如何安装使用 方式一CocoaPods推荐根据 JRSwizzle.podspec直接pod JRSwizzle方式二手动引入clone 仓库后把 JRSwizzle.h 和 JRSwizzle.m 加入工程即可git clone https://gitcode.com/gh_mirrors/jr/jrswizzle使用建议把交换调用放在load或initialize中执行且只交换一次——重复交换等于把实现换回去是最常见的自伤操作。十、总结一张表看懂全部机制 机制对应源码一句话解释查找方法L34-L52先校验找不到就报 NSError绝不留脏状态方法提升L54-L61两次class_addMethod把继承方法复制成类自己的实现交换L63method_exchangeImplementations只互换 IMP 指针旧系统兼容L66-L135无该 API 时手工提升 手动交换类方法交换L138-L140取元类后委托给实例方法交换Block 注入L142-L164Block → IMP → 挂类 → 交换NSInvocation 保留原实现JRSwizzle 的源码不长却把方法交换的每个坑都填平了先提升、再交换的六字心法配合完善的错误诊断和跨版本兼容让它成为学习 Objective-C 运行时方法交换机制的最佳活教材。读懂这 200 行代码你就真正掌握了method_exchangeImplementations的全套使用姿势。【免费下载链接】jrswizzleone-stop-shop for all your method swizzling needs项目地址: https://gitcode.com/gh_mirrors/jr/jrswizzle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表