方法魔法(Method Swizzling)

原理

交换两个方法的引用,就是调用a方法,实际执行b方法

func method_exchangeImplementations(_ m1: Method, _ m2: Method)

代码实现

+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError * *)error_ {

    Method origMethod = class_getInstanceMethod(self, origSel_);
    if (! origMethod) {
        SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
        return NO;
    }

    Method altMethod = class_getInstanceMethod(self, altSel_);
    if (! altMethod) {
        SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
        return NO;
    }

    class_addMethod(self,
                    origSel_,
                    class_getMethodImplementation(self, origSel_),
                    method_getTypeEncoding(origMethod));

    class_addMethod(self,
                    altSel_,
                    class_getMethodImplementation(self, altSel_),
                    method_getTypeEncoding(altMethod));

    method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
    return YES;

第三方库

RSSwizzle

安全隐患

  • 只在 + load 中执行 swizzling 才是安全的。 命名如果冲突将导致之前 hook 的失效 或者是循环调用。

说的是通常的 MethodSwizzling 是在分类里面实现的, 而分类的 Method 是被 Runtime 加载的时候追加到类的 MethodList ,如果不是在 + load 是执行的 Swizzling 一旦出现重名,那么 SEL 和 IMP 不匹配致 hook 的结果是循环调用。

  • 被 hook 的方法必须是当前类自身的方法,如果把继承来的 IMP copy 到自身上面会存在问题。父类的方法应该在调用的时候使用,而不是 swizzling 的时候 copy 到子类。

父类的分类加载不一定在子类之前

  • 被 Swizzled 的方法如果依赖与 cmd ,hook 之后 cmd 发送了变化,就会有问题(一般你 hook 的是系统类,也不知道系统用没用 cmd 这个参数)。

Objective-C Method 都会有两个隐含的参数 self, cmd,有的时候开发者在使用关联属性的适合可能懒得声明 (void *) 的 key,直接使用 cmd 变量 objc_setAssociatedObject(self, _cmd, xx, 0); 这会导致对当前 IMP 对 cmd 的依赖.如果系统方法中也使用了_cmd,那么将会导致关联错乱。

推荐阅读更多精彩内容