第一次接触黑魔法时,黑魔法就给自己留下了深刻的印象,
其核心思想就是,直接交换两个方法的实现。
我们先看这一小段的官方说明,
/**
* 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 else
,class_addMethod
和class_replaceMethod
,
我觉的懵逼的原因是,不能深刻理解第一版的缺陷,所以,第二版自然就懵逼了。
第二版,我详细解释一下:
- 既然第一版的缺陷是本类中可能没有
originalSelector
方法。第二版中,我首先尝试在本类中加一个originalSelector
方法,不用担心,如果本类中已经有originalSelector
的话,那么添加失败而已,返回NO
,就会执行method_exchangeImplementations
,那就跟第一版一模一样了。 - 你需要操心的是
class_addMethod
添加的SEL
是originalSelector
,
而IMP
却是swizzledSelector
的IMP
,不是他自己的IMP
,我称这种SEL
与IMP
不对应的方式为小三配。 - 经过
class_addMethod
添加成功以后,在内存中,originalSelector
和swizzledSelector
虽然方法名不同,但是,他们的IMP
却是同一个IMP
。 - 好吧,我们现在已经成功一半了,只要再将
swizzledSelector
的IMP
替换成originalSelector
的IMP
,就万事大吉了。 -
class_replaceMethod
用来将swizzledSelector
的IMP
替换为originalSelector
的IMP
。 - 第二版的hook已经完成了,他除了有些绕口难以理解以外,还是很健壮的。
但是这种方式太绕口了,很难理解,我想修改掉第二版中绕口的方式。
于是,我的第三版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,逻辑要比第二版的简单了许多,也没有那么多绕口了。
如果,我描述有描述的不清楚的,欢迎留言交流。