iOS底层原理:Method Swizzling原理和注意事项

Method Swizzling 是什么?

Method Swizzling的含义是方法交换,其核心内容是使用**runtime api**在运行时将一个方法的实现替换成另一个方法的实现。我们利用它可以替换系统或者我们自定义类的方法实现,进而达到我们的特殊目的,这就是我们常说的iOS黑魔法。

本文Demo地址:Github-JQMethodSwizzling

Method Swizzling 原理

OC方法在底层是通过**方法编号SEL 函数实现IMP一一对应进行关联的。打个比方,OC类好比一本书, SEL就像是书中的目录, IMP**相当于每条目录所对应的页码。关系如图所示:

方法交换的代码如下:

ini 复制代码
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swiSEL);
    method_exchangeImplementations(oriMethod, swiMethod);

交换后的关系如图所示:

从上述方法交换可以看出:

  • 交换的是两者的方法实现,也就是说调用**oriSEL方法时,最终走的方法实现是 swiIMP;调用 swiSEL方法时,最终走的方法实现是 oriIMP**。
  • 由此可见,在进行方法交换操作时,如果交换代码调用了两次或多次(2的倍数),就会导致方法实现又交换了回去,相当于交换了个寂寞,所以交换代码建议放在单例下进行来保证方法交换的有效性。

方法交换在使用中的递归调用分析

首先,我们来创建一个**JQStudent类,类中有两个实例方法, jq_studentInstanceMethod studentInstanceMethod;然后,在 load方法中对两个方法进行交换;最后, jq_studentInstanceMethod 的实现中再次调用 jq_studentInstanceMethod **方法。

代码实现如下图:

我们看到,这里会在**jq_studentInstanceMethod **方法中再次调用该方法,会不会引起递归调用呢?

运行结果如下图:

从运行结果看,并没有引起递归。这是因为进行方法交换后,在执行**[st studentInstanceMethod]时,实际上找到的是 jq_studentInstanceMethod 的方法实现,而 jq_studentInstanceMethod 方法实现中又执行 [self jq_studentInstanceMethod],同样是因为方法交换,此时 jq_studentInstanceMethod的方法实现也已经指向了 studentInstanceMethod,所以并不会引起递归调用。相反,如果我们在 jq_studentInstanceMethod 方法中调用了 [self studentInstanceMethod]**才是会引起递归调用的,小伙伴们一定要注意!!! 流程如下图:

在实际的开发中,我们常采用这种方式对业务流程中的一些关键方法进行方法交换(俗称hook),从而达到不影响业务流程的情况下完成一些信息的收集工作,而这种方式则被称为**AOPAspect Oriented Programming,面向切面编程)。 AOP是一种编程的思想,区别于 OOPObject Oriented Programming,面向对象编程)。其实 OOP AOP都是一种编程的思想,只不过 OOP编程思想更加倾向于对业务模块的封装,划分出更加清晰的逻辑单元。而 AOP**则是面向切面进行提取封装,提取各个模块中的公共部分,提高模块的复用率,降低业务之间的耦合性。

方法交换的坑点和分析

坑点一:交换父类的方法

我们还在刚才的Demo中来演示,现在有一个**JQStudent类了,再创建一个 JQPerson类,让 JQStudent继承 JQPerson,在 JQPerson类添加一个实例方法 personInstanceMethod,在 JQStudent类的 load方法中将 jq_studentInstanceMethod 方法和父类中的 personInstanceMethod**方法进行交换。

实现代码如下图:

运行结果如下图:

从上面的结果可以看到:

  • 子类**JQStudent对象调用父类 JQPerson的方法 personInstanceMethod**,消息发送会通过方法查找从而找到父类方法并调用。
  • 但是此时父类**JQPerson中的方法 personInstanceMethod对应的方法实现已经被交换成了子类 JQStudent jq_studentInstanceMethod,因此会执行子类的 jq_studentInstanceMethod**方法实现。
  • 同理,此时子类中调用**jq_studentInstanceMethod方法,会执行父类的 personInstanceMethod**方法实现。

这样看起来好像没有什么问题啊!紧接着,我们再使用父类**JQPerson对象调用一下 personInstanceMethod**方法,如下图:

啪、啪、啪,报错了!!!我们来分析下什么原因,

  • 首先,父类调换用**personInstanceMethod方法会执行子类中的 jq_studentInstanceMethod**方法实现。
  • 然后又调用了**jq_studentInstanceMethod方法,但是,此时的调用者是 JQPerson对象,父类 JQPerson中并没有 jq_studentInstanceMethod**方法实现。所以因方法找不到而报错。

出了问题,我们来解决以下,将交换方式换成下面这种:

再看运行结果:

此时,我们的运行不报错了,而且**JQStudent对象调用父类的 personInstanceMethod方法,确实走了方法交换后的流程, JQPerson对象也正常的调用了 personInstanceMethod**方法,互不影响。为什么呢? 原因是:

  1. 在方法交换前,先尝试给本类添加一下**oriSEL方法,方法实现为 swiMethod**;
  2. 如果添加成功则返回**YES,代表本类中原本没有 oriSEL的方法实现;接着,再将父类的方法实现 oriMethod替换给本类的 swiSEL**;
  3. 添加失败则返回**NO,代表本类中已有 oriSEL**的方法实现,进行正常的方法交换即可。

坑点二:交换的父类中并没有实现的方法

如果要交换的父类方法并没有实现呢?直接看下运行结果:

什么情况?我的天,递归了!!!为什么呢?我们断点调试一下,看图解释:

从上面这些坑中,我们可以得出一些结论:

  • 方法交换要遵循功能单一原则,也就是说本类交换本类中的方法,不能影响父类,否则会影响父类和兄弟姐妹的行为(方法);
  • 即使要交换父类的方法,也要在本类中实现(重写)父类的方法;
  • 本类或父类交换的方法实现不存在,要给本类添加这个方法实现,否则会出现递归调用

基于以上特点,我封装一个更好的方法交换方式,请看以下代码实现:

运行结果如下:

相关推荐
蒲公英少年带我飞3 小时前
iOS底层原理:KVC分析
ios
2501_915921434 小时前
iOS混淆与IPA加固全流程(iOS混淆 IPA加固 Ipa Guard实战)
android·ios·小程序·https·uni-app·iphone·webview
游戏开发爱好者84 小时前
iOS 26 App 开发阶段性能优化 从多工具协作到数据驱动的实战体系
android·ios·小程序·uni-app·iphone·webview·1024程序员节
2501_915106324 小时前
深入剖析 iOS 26 系统流畅度,多工具协同监控与性能优化实践
android·ios·性能优化·小程序·uni-app·cocoa·iphone
蒲公英少年带我飞5 小时前
iOS底层原理:OC对象底层探索之alloc初探
ios
Digitally6 小时前
如何准备 iPhone 以旧换新:10 个重要提示
ios·iphone
Tlaster7 小时前
使用KMP实现原生UI + Compose混合的社交客户端
android·ios·开源
游戏开发爱好者87 小时前
iOS 26 App 查看电池寿命技巧,多工具组合实践指南
android·macos·ios·小程序·uni-app·cocoa·iphone
linghugoogle11 小时前
基于 Metal 的 iOS 全景视频播放器
ios