在iOS开发中,有一种编程方式叫面向切面编程(AOP),这种编程最大好处是,在不修改源代码的前提下,在原有功能上添加新功能,常见的就是埋点需求。今天要说的是,实现AOP时常用的方法,OC运行时的方法交换(swizzleMethod)。
说到方法交换,这里就有两个东西要说:SEL和IMP。SEL其实就是一个整形标识,用来唯一标识一个方法名而已。而IMP是一个函数指针,表示方法实现的代码块地址。每一个SEL都会对应一个IMP,但是,IMP可能会被多个SEL对应。当做方法交换时,其实交换的是SEL与IMP的对应关系,如下图。
交换后的效果就是,当调用对象的selectorA方法时,执行的是IMPb代码块的实现。用到的相关代码如下:
static inline void swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
上面提到面向切面编程,那么仅仅交换方法是不够的,还需要返回原函数。这时,往往是在IMPb实现里面调用self selectorB,表面看方法里面调用自己,会出现死循环,但由于做了方法交换,实际的情况就是这样的:当调用对象selectorA方法时,执行的是IMPb的实现,而IMPb实现中又调用了selectorB,由于做了方法交换,此时执行的是IMPa的实现,这样就达到了在调用selectorA前先执行了selectorB对应的IMPb的实现,然后又继续执行IMPa的实现。
通常以上实现会借助类别,在load方法中,执行类内方法与类别新定义方法进行交换,类别新方法调用自身方法达到回到原函数的效果。实际交换的对象是本类的方法。除此之外,方法交换也可以交换父类与子类方法,也可以没有关系的两个类之间方法。
下面主要说说在使用方法交换的时候,需要注意以下几点:
-
一、类A与类B无继承关系,通常有以下几种情况:
a、类A有funA,类B有funB,先将类B的funB添加到类A,然后类A的funA与类B的funB交换。这时A对象的funA的实现是类B的funB的IMP,A对象的funB的实现是类B的funB的IMP,B对象的funB的实现是类A的funA的IMP。
b、类A的funA与类B的funB进行方法交换时,如果在funA中有self funA或者funB中有self funB这样的实现时,在交换方法时,要先添加本类没有的方法,否则在调用self funA或者self funB时,会出现异常,没有对应的selector。
c、如果类A有funB的声明,单没有实现,这时,A类可以动态添加funB的实现;如果类A有funB的实现,这时往A里再添加funB的实现,会失败,此时可以做方法交换。
-
二、类A与类B有继承关系,且B继承A。这时有几种情况:
a、类A有funA,类B有funB,将funA与funB做方法交换,然后A与B的对象调用各自的方法,此时的实现是被交换的,没有问题。
b、类A有funA,类B有funA,且B的funA调用了super funA,将A的funA与B的funA做方法交换,此时当A对象调用funA时,就会出现问题,因为funA执行的是的IMPb,而funB又调用了self funA,此时的self是A的实例,这样就又会执行IMPb,形成循环。
c、类A有funA,类B有funA,且B的funA没有调用super funA,将A的funA与B的funA做方法交换,效果和a情况相同,没有问题。
由于Runtime的方法交换用处比较多,本人经验有限,也希望大家提出意见和新的用法。本人也会在今后的开发中补充此文章。
所有代码可以到本的Github下载。