MethodSwizzling的实现
首先oc中的方法,是一个 objc_method
结构体,由三部分组成
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name // 方法sel
char * _Nullable method_types // 方法参数
IMP _Nonnull method_imp // 方法实现
}
IMP
指向了方法的实现
我们常使用objc/runtime.h
中提供的api:method_exchangeImplementations(Method m1, Method m2)
来交换IMP,重新绑定sel和 IMP对应关系
Method oriMethod = class_getInstanceMethod(self, @selector(method1));
Method swiMethod = class_getInstanceMethod(self, @selector(method2));
method_exchangeImplementations(oriMethod, swiMethod);
但class_getInstanceMethod
函数,在当前类没有实现方法时,会返回父类的方法
method_exchangeImplementations
函数仅仅交换了两个 Method 结构体的 imp,上面的代码,当子类没有实现oriMethod
时会将父类和子类方法的实现互换.
因此可以使用下面的方式
Method oriMethod = class_getInstanceMethod(self, @selector(method1));
Method swiMethod = class_getInstanceMethod(self, @selector(method2));
method_exchangeImplementations(oriMethod, swiMethod);
// 添加方法
if(class_addMethod(self, @selector(method1), method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod))){
// 如果添加成功了,当前类没有该方法,需要把swiMethod的实现替换成oriMethod的.
class_replaceMethod(self, @selector(method2), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}
else{
// 如果添加失败了,当前类有这个方法
method_exchangeImplementations(oriMethod, swiMethod);
}
或者使用下面这种方式
// 确保当前类,两个方法都实现了,再去交换
Method oriMethod = class_getInstanceMethod(self, @selector(method1));
Method swiMethod = class_getInstanceMethod(self, @selector(method2));
class_addMethod(self,
@selector(method1),
class_getMethodImplementation(self, @selector(method1)),
method_getTypeEncoding(oriMethod));
class_addMethod(self,
@selector(method2),
class_getMethodImplementation(self, @selector(method2)),
method_getTypeEncoding(swiMethod));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(method1)), class_getInstanceMethod(self, @selector(method1)));
隐患
只在 +load 中执行 swizzling 才是安全的。
被 hook 的方法必须是当前类自身的方法,如果把继承来的 IMP copy 到自身上面会存在问题。父类的方法应该在调用的时候使用,而不是 swizzling 的时候 copy 到子类。
被 Swizzled 的方法如果依赖 cmd ,hook 之后 cmd 发送了变化,就会有问题(一般你 hook 的是系统类,也不知道系统用没用 cmd 这个参数)。
命名如果冲突导致之前 hook 的失效 或者是循环调用。
第一条和第四条说的是通常 MethodSwizzling 是在分类里面实现的, 而分类的 Method 是被Runtime 加载的时候追加到类的 MethodList ,如果不在 +load
中执行, Swizzling 一旦出现重名,那么 SEL 和 IMP 不匹配致 hook 的结果是循环调用。
第三条是一个不容易被发现的问题。
我们都知道 Objective-C Method 都会有两个隐含的参数 self
,cmd
,有的时候开发者在使用关联属性的适合可能懒得声明 (void *) 的 key,直接使用 cmd 变量 objc_setAssociatedObject(self, _cmd, xx, 0); 这会导致对当前IMP对 cmd 的依赖。
一旦此方法被 Swizzling,那么方法的 cmd 势必会发生变化,出现了 bug 之后想必一定找不到,
Copy父类的方法带来的隐患
上面的两种 Swizzling 方式,存在copy父类方法带来的隐患
当父类中存在一个 方法.
子类的分类 hook 父类方法时,会把父类实现 copy到子类.
这时父类的分类再去 hook 父类的 方法,会导致子类 Hook 不到父类的分类的实现
这时可以使用RSSwizzle
RSSwizzleInstanceMethod([Student class],
@selector(sayHello),
RSSWReturnType(void),
RSSWArguments(),
RSSWReplacement(
{
// 调用之前的实现
RSSWCallOriginal();
// 自身的实现
NSLog(@"Student + swizzle say hello sencod time");
}), 0, NULL);
RSSwizzleInstanceMethod([Person class],
@selector(sayHello),
RSSWReturnType(void),
RSSWArguments(),
RSSWReplacement(
{
// 调用之前的实现
RSSWCallOriginal();
// 自身的实现
NSLog(@"Person + swizzle say hello");
}), 0, NULL);
将宏展开
// 创建block
RSSwizzleImpFactoryBlock newImp = ^id(RSSwizzleInfo *swizzleInfo) {
void (*originalImplementation_)(__unsafe_unretained id,SEL));
SEL selector_ = @selector(sayHello);
return ^void (__unsafe_unretained id self) {
// 调用父类的实现
((__typeof(originalImplementation_))[swizzleInfo getOriginalImplementation])(self, selector_);
//只有这一行是我们的核心逻辑
NSLog(@"Student + swizzle say hello");
};
};
[RSSwizzle swizzleInstanceMethod:@selector(sayHello)
inClass:[[Student class] class]
newImpFactory:newImp
mode:0 key:((void*)0)];;
其中RSSwizzleInfo
的声明和 swizzleInstanceMethod:inClass:newImpFactory:newImp:mode:key:
方法的实现
/*
其中
typedef id (^RSSwizzleImpFactoryBlock)(RSSwizzleInfo *swizzleInfo);
*/
@interface RSSwizzleInfo : NSObject
/**
返回swizzled方法的原始实现。
如果swizzled类实现了方法本身,那么它实际上是一个原始实现;如果没有实现方法,则是从父类获取的实现。
@注意:调用时,必须始终将返回的实现强制转换为相应的函数指针。
@返回指向swizzled方法的原始实现的函数指针。
*/
-(RSSwizzleOriginalIMP)getOriginalImplementation;
/// The selector of the swizzled method.
@property (nonatomic, readonly) SEL selector;
@end
*/
typedef NS_ENUM(NSUInteger, RSSwizzleMode) {
///RSSwizzle总是做swizzing。
RSSwizzleModeAlways = 0,
/// 如果以前用同一个键 RSSwizzle 过同一个类,就不会RSSwizzle。
RSSwizzleModeOncePerClass = 1,
/// 如果同一个类或父类之前已经用同一个键进行了swizzle,就不会RSSwizzle。
RSSwizzleModeOncePerClassAndSuperclasses = 2
};
+(BOOL)swizzleInstanceMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
mode:(RSSwizzleMode)mode
key:(const void *)key
{
if (key){
// 获得key对应的set
NSSet *swizzledClasses = swizzledClassesForKey(key);
if (mode == RSSwizzleModeOncePerClass) {
// 是否用同一个key RSSwizzle 过同一个类
if ([swizzledClasses containsObject:classToSwizzle]){
return NO;
}
}else if (mode == RSSwizzleModeOncePerClassAndSuperclasses){
for (Class currentClass = classToSwizzle;
nil != currentClass;
currentClass = class_getSuperclass(currentClass))
{
if ([swizzledClasses containsObject:currentClass]) {
return NO;
}
}
}
}
swizzle(classToSwizzle, selector, factoryBlock);
if (key){
// 保存key
[swizzledClassesForKey(key) addObject:classToSwizzle];
}
}
return YES;
}
RSSwizzle核心代码其实只有一个函数,
// 删除无关的加锁,防御逻辑,简化理解。
static void swizzle(Class classToSwizzle,
SEL selector,
RSSwizzleImpFactoryBlock factoryBlock)
{
Method method = class_getInstanceMethod(classToSwizzle, selector);
__block IMP originalIMP = NULL;
RSSWizzleImpProvider originalImpProvider = ^IMP{
// 保存 class_replaceMethod 获得之前的实现,如果之前没有实现,
IMP imp = originalIMP;
// 如果imp不存在,则获取父类的方法的imp.
if (NULL == imp){
imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
}
return imp;
};
RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
swizzleInfo.selector = selector;
swizzleInfo.impProviderBlock = originalImpProvider;
// 执行传进来的block, 获得 替换后的实现
id newIMPBlock = factoryBlock(swizzleInfo);
// 获得返回值类型
const char *methodType = method_getTypeEncoding(method);
// 创建实现
IMP newIMP = imp_implementationWithBlock(newIMPBlock);
// 替换方法,并获得之前的实现
originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
}
通过阅读源码可知, RSSwizzle 并没有去交换方法的imp,而是,保存旧的imp, 然后新的方法替换了旧的方法.