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

2022-03-29  本文已影响0人  蒲公英少年带我飞

Method Swizzling 是什么?

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

本文Demo地址:Github-JQMethodSwizzling

Method Swizzling 原理

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

1.png

方法交换的代码如下:

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swiSEL);
    method_exchangeImplementations(oriMethod, swiMethod);

交换后的关系如图所示:

2.png

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

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

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

代码实现如下图:

3.png

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

运行结果如下图:

4.png

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

5.png

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

方法交换的坑点和分析

坑点一:交换父类的方法

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

实现代码如下图:

6.png 7.png

运行结果如下图:

8.png

从上面的结果可以看到:

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

9.png

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

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

10.png 11.png

再看运行结果:

12.png

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

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

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

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

13.png 14.png

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

15.png 16.png

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

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

17.png

运行结果如下:

18.png
上一篇 下一篇

猜你喜欢

热点阅读