前言
刚才翻代码时发现N年前写的方法交换,当时方法交换还是个新奇的东东,网上找了一番发现都有各种问题,于是动手写了一个。如今方法交换的写法已经烂大街了,但把我当时写的拿出来一对比,不得不自吹一句,还是我自己写的更优雅。
上代码
我当年的实现代码
static void exchangeSelector(Class oClass, SEL oSelector, Class sClass, SEL sSelector) {
Method originalMethod = class_getInstanceMethod(oClass, oSelector);
Method swizzledMethod = class_getInstanceMethod(sClass, sSelector);
IMP oIMP = method_getImplementation(originalMethod);
IMP sIMP = method_getImplementation(swizzledMethod);
const char *oType = method_getTypeEncoding(originalMethod);
const char *sType = method_getTypeEncoding(swizzledMethod);
class_replaceMethod(oClass, oSelector, sIMP, sType);
class_replaceMethod(oClass, sSelector, oIMP, oType);
}
与某流行的代码对比
放在一起看,单论颜值就不是一个档次的
分析
目的
我们使用方法交换的目的实际是修改方法,当然还要保持原方法的实现可被利用。
如图:新方法的实现和原方法交换后,通过新方法名就可以使用原本的实现。
问题
而出现的BUG,通常是原方法的实现并不在类本身,而是在父类中,替换的时候影响到了父类
我们想要的结果
处理过程
理论处理
大象装冰箱分几步?1.获取大象,2.放进冰箱。
我们如何交换变量呢?1.获取旧值,2.放进新变量。下面的写法最容易理解了吧。
{
//交换a, b两个变量的值;
tmp1 = a;
tmp2 = b;
a = tmp2;
b = tmp1;
}
接下来是交换方法,我们如何交换方法呢?1.获取旧实现,2.放进新方法。
把class比喻成字典,把方法名比喻成key,把方法实现比喻成value,把class有父类比喻字典也像NSUserDefaults一样有更深的域,那么我们要做的事情就是这个
exchangeSelector(funA, funB) {
class = self.class;
methodA = class[funA];
methodB = class[funB];
class[funA] = methodB;
class[funB] = methodA;
}
再复杂点,methodB 没必要限定在本类,毕竟要加一堆类别也挺烦的,而且有些类是隐藏的,强行声明出来再加类别扩展方法总感觉很不靠谱。于是我们指定 methodB ,由于methodB的获取不是常用方法,我们换成把获取 methodB 所需的条件传入
exchangeSelector(class, funA, classForMethodB, funForMethodB) {
methodA = class[funA];
methodB = classForMethodB[funForMethodB];
class[funA] = methodB;
class[funB] = methodA;
}
我当年的处理
就是按上面最单纯的过程,换成最直白的代码。
- 获取实现
//原实现
Method originalMethod = class_getInstanceMethod(oClass, oSelector);
IMP oIMP = method_getImplementation(originalMethod);
const char *oType = method_getTypeEncoding(originalMethod);
//新实现
Method swizzledMethod = class_getInstanceMethod(sClass, sSelector);
IMP sIMP = method_getImplementation(swizzledMethod);
const char *sType = method_getTypeEncoding(swizzledMethod);
- 把实现放进方法。
//把新实现放进原方法
class_replaceMethod(oClass, oSelector, sIMP, sType);
//把原实现放进新方法。这里要注意,我们的目的只是替换原本类里的方法。
class_replaceMethod(oClass, sSelector, oIMP, oType);
什么?你问上面那个BUG的问题,原方法子类里没实现,是在父类实现的怎么办?
如果让你给一个变量赋值,你是这样做
int a; //假如有一个变量a,我们要给它赋值
if (a) { //先判断a原本是否有值
a = 1; //如果a已经有值了,就将a的旧值改成新值
} else {
a = 1; //如果a没值,则直接使用新值
}
还是这样做
int a; //假如有一个变量a,我们要给它赋值
a = 1; //不判断a当前是否有值,直接赋值
我选择后者。
如果你来设计一个函数,用来设置字典中某个key所对应的值,你会设计成当别人使用时,需要像左边这样先判断当前是否有值,再根据不同的情况进行不同的处理,还是向右边这样一步搞定?
我还是会选择后者,我问过别人,非常巧合的是他们也选择后者,更巧合的是 class_replaceMethod 也和我们一样选择了后者。所以我们根本不用考虑子类里有没有实现这种事情。
某流行处理
不多说了,除了 class_replaceMethod 还要了解 class_addMethod、method_exchangeImplementations ,然后再做判断,不同分支做不同处理,看起来就容易令人迷惑的样子。