iOS进阶ios实用开发技巧eiYo

iOS方法交换(黑魔法)也可以这样用

2018-12-10  本文已影响108人  xiaogangsi

第一次接触黑魔法时,黑魔法就给自己留下了深刻的印象,
其核心思想就是,直接交换两个方法的实现。
我们先看这一小段的官方说明,

/** 
 * Exchanges the implementations of two methods.
 * 
 * @param m1 Method to exchange with second method.
 * @param m2 Method to exchange with first method.
 * 
 * @note This is an atomic version of the following:
 *  \code 
 *  IMP imp1 = method_getImplementation(m1);
 *  IMP imp2 = method_getImplementation(m2);
 *  method_setImplementation(m1, imp2);
 *  method_setImplementation(m2, imp1);
 *  \endcode
 */
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

文档中有四行代码,已经描述的很清楚了
很简单的实现原理,我们都能看懂

在开发中,为了方便自己使用,我的第一版hook是这样写的,

+ (void)hookClass:(Class)classObject originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Method method1 = class_getInstanceMethod(classObject, originalSelector);
    Method method2 = class_getInstanceMethod(classObject, swizzledSelector);
    method_exchangeImplementations(method1, method2);
}

这段代码,大家肯定也能看出来其弊端吧,有时候不太好使,
原因是如果classObject中,没有originalSelector选择器,
返回的可能就是其基类中的originalSelector
那我们直接交换的话,就是交换基类的方法,
这样,就与我的初衷背道而驰了,

后来,我的第二版hook就变成了这个样子,
这个版本的hook,我最早是在AFNetworking中发现的。

+ (void)hookClass:(Class)classObject originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
   Class class = classObject;
   
   Method originalMethod = class_getInstanceMethod(class, originalSelector);
   Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
   
   if(class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
       class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
   } else {
       method_exchangeImplementations(originalMethod, swizzledMethod);
   }
}

对于第一版hook,我相信绝大多数iOS开发者都能看懂,
对于第二版hook,有几个iOS开发者能不懵逼呢?

第二版的hook,相比较第一版的hook而言,只是增加了一个if elseclass_addMethodclass_replaceMethod
我觉的懵逼的原因是,不能深刻理解第一版的缺陷,所以,第二版自然就懵逼了。

第二版,我详细解释一下:

但是这种方式太绕口了,很难理解,我想修改掉第二版中绕口的方式。
于是,我的第三版hook是这样写的

+ (void)hookClass:(Class)classObject originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Class class = classObject;
    //第三版Hook核心思想是,
    //如果本类中有 originalSelector 方法,直接交换。
    //如果本类中没有 originalSelector 方法,添加父类的的方法到子类,然后再直接交换。
    
    // 这两行与以前没有任何变化
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    //class_addMethod 添加的SEL是 originalSelector ,IMP 是 method_getImplementation(originalMethod),我称他们为原配,
    //如果本类中,已经有 originalSelector,再添加 originalSelector, 肯定返回NO,添加失败,那就直接交换了,跟第一版hook流程一模一样了,
    //如果本类中,没有此SEL,那就会去父类里找,返回的就是父类里的信息,然后将父类的信息,添加到本类中,就相当于,本类完全的继承了父类的方法,
    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    if (didAddMethod) {
        //添加成功后,本类中,已经有一个originalSelector方法了
        //我们第一次获得originalMethod是返回父类的originalMethod
        //我们需要再重新获得一下originalMethod,这次返回的不是父类的originalMethod
        //而是我们刚刚class_addMethod添加的到本类的originalMethod
        
        originalMethod = class_getInstanceMethod(class, originalSelector);
    }
    
    //走到这,就证明了,本类中肯定已经有这两个方法了,那就这样直接交换吧。
    method_exchangeImplementations(swizzledMethod, originalMethod);
}

从我来看,第三版的hook,逻辑要比第二版的简单了许多,也没有那么多绕口了。

如果,我描述有描述的不清楚的,欢迎留言交流。

上一篇下一篇

猜你喜欢

热点阅读