iOS 方法交换的基本方法

426 阅读3分钟

交换已知类的实例方法:

  1. 给已知的类添加一个 category ,实现你想要hook的方法。
  2. 使用如下函数即可完成方法调换
+ (BOOL)swizzleClass:(Class)clazz sel:(SEL)origSEL withSel:(SEL)swizzlingSEL {
    Method orgMethod = class_getInstanceMethod(clazz, origSEL);
    Method swizzlingMethod = class_getInstanceMethod(clazz, swizzlingSEL);
    if (!orgMethod || !swizzlingMethod) {
        return NO;
    }
    BOOL added = class_addMethod(clazz, origSEL,
                                 method_getImplementation(swizzlingMethod),
                                 method_getTypeEncoding(swizzlingMethod));
    if (added) {
        // 返回成功,说明origSEL只是基类的实现,并且已经在当前类添加了实现,实现指向的是swizzlingMethod。
        // 这时,只需要把swizzlingSEL指向原实现即可完成交换
        class_replaceMethod(clazz, swizzlingSEL,
                            method_getImplementation(orgMethod),
                            method_getTypeEncoding(orgMethod));
    } else {
        // 返回失败,说明origSEL已经在当前类实现了,直接交换即可。
        method_exchangeImplementations(orgMethod, swizzlingMethod);
    }
    return YES;
}

说明:为什么不能直接method_exchangeImplementations 呢?

因为,如果是基类实现的原方法,那么,直接交换的话,会导致某个基类的实现被交换了,其他只用到基类(而不是当前交换类)的方法调用也会进入我们的交换方法里了。

举例

我们想要观察一些微信SDK打开微信的时候传递了什么参数,我们就可以通过调换UIApplication的openURL方法,增加日志来实现

@interface UIApplication (SwizzeTest)
@end
@implementation UIApplication (SwizzeTest)
- (BOOL)my_openURL:(NSURL *)url
{
    NSLog(@"openURL:%@", url);
    return [self my_openURL:url];
}
@end
[HooTools swizzleClass:[UIApplication class] sel:@selector(openURL:) withSel:@selector(my_openURL:)];

交换类的方法

交换类的方法和交换类的实例方法的原理是一样的,只是操作的对象不是Class而是Meta Class。 具体的代码如下

+ (void)swizzleClassMethodForClass:(Class)className sel:(SEL)orgSel withSel:(SEL)swizzlingSel
{
    // 交换实例方法
    Method originalMethod = class_getClassMethod(className, orgSel);
    Method swizzledMethod = class_getClassMethod(className, swizzlingSel);
    if (!originalMethod || !swizzledMethod) {
        return;
    }
    Class metaClass = object_getClass(className);
    BOOL added = class_addMethod(metaClass, orgSel,
                                 method_getImplementation(swizzledMethod),
                                 method_getTypeEncoding(swizzledMethod));
    if (added) {
        class_replaceMethod(metaClass, swizzlingSel,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    }else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

交换未知类的方法

我们在跟踪一些问题的时候,经常会发现OC内部其实有很多内部实现的类型,比如NSString *str = @"aa"; str对象内部的实际类型是__NSCFConstantString。

如果我们直接去交换NSString的方法,是达不到我们想要的目的的。

这时候,需要用改进一下上面的交换方法的实现,让它能够支持替换成完全另一个类的方法。

实现如下:

+ (BOOL)swizzleClassNamed:(NSString *)orgClassName
              methodNamed:(NSString *)orgMethodName
                withClass:(Class)swizzlingClass
              andSelector:(SEL)swizzlingSEL
{
    Class orgClass = NSClassFromString(orgClassName);
    SEL orgSEL = NSSelectorFromString(orgMethodName);
    Method orgMethod = class_getInstanceMethod(orgClass, orgSEL);
    Method swizzlingMethod = class_getInstanceMethod(swizzlingClass, swizzlingSEL);
    if (!orgMethod || !swizzlingMethod) {
        return NO;
    }
    BOOL added = class_addMethod(orgClass, swizzlingSEL, method_getImplementation(orgMethod), method_getTypeEncoding(orgMethod));
    // 这里肯定是要成功的,否则就是存在同名的方法了,需要改名处理一下
    if (!added) {
        return NO;
    }
    added = class_addMethod(orgClass, orgSEL, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
    if (!added) {
        // 不成功,说明当前类已经有实现了原方法的,直接对调此方法和交换方法即可。
        method_exchangeImplementations(orgMethod, swizzlingMethod);
    }
    // 如果是成功的话,就是完成了向当前类添加原方法,实现指向交换方法的实现。
    // 配合前面addMethod的结果,当前类添加了交换方法,实现指向原方法的实现。
    return YES;
}

举例

假如,我们遇到了一个第三方SDK使用 NSURLComponents 的 setPercentEncodedQuery 方法时候产生的崩溃问题,我们希望看看到底是什么参数设置导致的崩溃。

从崩溃的日志看,其内部类型是__NSConcreteURLComponents,通过上述方法在自己的类实现一个swizzling_setPercentEncodedQuery,并hook到我们实现的这个方法:

- (void)swizzling_setPercentEncodedQuery:(NSString *)query
{
    NSLog(@"swizzling_setPercentEncodedQuery:%@", query);
    [self swizzling_setPercentEncodedQuery:query];
}

[HooTools swizzleClassNamed:@"__NSConcreteURLComponents" methodNamed:@"setPercentEncodedQuery:" withClass:[self class] andSelector:@selector(swizzling_setPercentEncodedQuery:)];

补充

Github上有一些功能更加完毕的开源库