method-swizzling 是什么?
method-swizzling的含义是方法交换,其主要作用是在运行时将一个方法的实现替换成另一个方法的实现,这就是我们常说的iOS黑魔法,
在OC中就是利用method-swizzling实现AOP,其中AOP(Aspect Oriented Programming,面向切面编程)是一种编程的思想,区别于OOP(面向对象编程)
OOP和AOP都是一种编程的思想ios_lowLevel
OOP编程思想更加倾向于对业务模块的封装,划分出更加清晰的逻辑单元;
而AOP是面向切面进行提取封装,提取各个模块中的公共部分,提高模块的复用率,降低业务之间的耦合性。
每个类都维护着一个方法列表,即methodList,methodList中有不同的方法即Method,每个方法中包含了方法的sel和IMP,方法交换就是将sel和imp原本的对应断开,并将sel和新的IMP生成对应关系
method-swizzling涉及的相关API
通过sel获取方法Method
class_getInstanceMethod:获取实例方法
class_getClassMethod:获取类方法
method_getImplementation:获取一个方法的实现
method_setImplementation:设置一个方法的实现
method_getTypeEncoding:获取方法实现的编码类型
class_addMethod:添加方法实现
class_replaceMethod:用一个方法的实现,替换另一个方法的实现,即aIMP 指向 bIMP,但是bIMP不一定指向aIMP
method_exchangeImplementations:交换两个方法的实现,即 aIMP -> bIMP, bIMP -> aIMP
坑点1:method-swizzling使用过程中的一次性问题
所谓的一次性就是:mehod-swizzling写在load方法中,而load方法会主动调用多次,这样会导致方法的重复交换,使方法sel的指向又恢复成原来的imp的问题
解决方案
可以通过单例设计原则,使方法交换只执行一次,在OC中可以通过dispatch_once实现单例
坑点2:子类没有实现,父类实现了
在下面这段代码中,LGPerson中实现了personInstanceMethod,而LGStudent继承自LGPerson,没有实现personInstanceMethod,运行下面这段代码会出现什么问题?
下面是封装好的method-swizzling方法
通过实际代码的调试,s调用personInstanceMethod 正常 而在p调用personInstanceMethod方法时崩溃,
[s personInstanceMethod];中不报错是因为student中的imp交换成了lg_studentInstanceMethod,而LGStudent中有这个方法(在LG分类中),所以不会报错
崩溃的点在于[p personInstanceMethod];,其本质原因:LGStudent的分类LG中进行了方法交换,将person中imp交换成了LGStudent中的lg_studentInstanceMethod,然后需要去LGPerson中的找lg_studentInstanceMethod,但是LGPerson中没有lg_studentInstanceMethod方法,即相关的imp找不到,所以就崩溃了
优化:避免imp找不到
通过class_addMethod尝试添加你要交换的方法
如果添加成功,即类中没有这个方法,则通过class_replaceMethod进行替换,其内部会调用class_addMethod进行添加
如果添加不成功,即类中有这个方法,则通过method_exchangeImplementations进行交换
下面是class_replaceMethod、class_addMethod和method_exchangeImplementations的源码实现
其中class_replaceMethod和class_addMethod中都调用了addMethod方法,区别在于bool值的判断,下面是addMethod的源码实现
坑点3:子类没有实现,父类也没有实现,下面的调用有什么问题?
经过调试,发现运行代码会崩溃
原因是栈溢出,递归死循环了,那么为什么会发生递归呢?----主要是因为personInstanceMethod没有实现,然后在方法交换时,始终都找不到oriMethod,然后交换了寂寞,即交换失败,当我们调用personInstanceMethod(oriMethod)时,也就是oriMethod会进入LG中lg_studentInstanceMethod方法,然后这个方法中又调用了lg_studentInstanceMethod,此时的lg_studentInstanceMethod并没有指向oriMethod,然后导致了自己调自己,即递归死循环
优化:避免递归死循环
如果oriMethod为空,为了避免方法交换没有意义,而被废弃,需要做一些事情
通过class_addMethod给oriSEL添加swiMethod方法
通过method_setImplementation将swiMethod的IMP指向不做任何事的空实现