美文网首页
OC方法交换的优雅实现

OC方法交换的优雅实现

作者: 梨花树下好乘凉 | 来源:发表于2021-07-27 22:51 被阅读0次

    前言

    刚才翻代码时发现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);
        
    }
    

    与某流行的代码对比

    放在一起看,单论颜值就不是一个档次的

    16274538549564.jpg

    分析

    目的

    我们使用方法交换的目的实际是修改方法,当然还要保持原方法的实现可被利用。
    如图:新方法的实现和原方法交换后,通过新方法名就可以使用原本的实现。

    16273935303846.jpg

    问题

    而出现的BUG,通常是原方法的实现并不在类本身,而是在父类中,替换的时候影响到了父类

    16273940647544.jpg

    我们想要的结果

    16274438965756.jpg

    处理过程

    理论处理

    大象装冰箱分几步?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;
    }
    

    我当年的处理

    就是按上面最单纯的过程,换成最直白的代码。

    1. 获取实现
    16273944303240.jpg
        //原实现
        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);
    
    1. 把实现放进方法。
    16273945396107.jpg
        //把新实现放进原方法
        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所对应的值,你会设计成当别人使用时,需要像左边这样先判断当前是否有值,再根据不同的情况进行不同的处理,还是向右边这样一步搞定?

    16273964339426.jpg

    我还是会选择后者,我问过别人,非常巧合的是他们也选择后者,更巧合的是 class_replaceMethod 也和我们一样选择了后者。所以我们根本不用考虑子类里有没有实现这种事情。

    某流行处理

    不多说了,除了 class_replaceMethod 还要了解 class_addMethod、method_exchangeImplementations ,然后再做判断,不同分支做不同处理,看起来就容易令人迷惑的样子。

    相关文章

      网友评论

          本文标题:OC方法交换的优雅实现

          本文链接:https://www.haomeiwen.com/subject/mpiomltx.html