关于不同类的Method Swizzling

iOS的Method Swizzling是一个非常有意思的run time应用案例.用它可以实现AOP,也可以用来hook很多API,进行很多hack的操作

相关资料很多,总体来讲就是调换两个Method的Imp,也就是调换函数指针.在很多AOP的案例中,使用这种方式hook住关键方法,在递归调用方法前/后实现AOP.例如:在一个UIViewController的category中,可以这样

+ (void)load {
    Method currentDidLoadMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoadAOP));
    Method oriDidLoadMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
    method_exchangeImplementations(currentDidLoadMethod, oriDidLoadMethod);
}
- (void)viewDidLoadAOP {
   //在这里写代码,可以类似成为Before
   [self viewDidLoadAOP];//这里递归调用自己
   //在这里写代码,可以类似成为After

但是如果交换不同类之间的方法,这样调用就会crash.
比如有A,B两个类,分别有a方法和b方法.我们调用A类的a方法,希望通过method swizzling对A方法进行处理.

调用A类的a方法

A *aClass = [A new];
[aClass a];

a方法

- (void)a {
 NSLog(@"a");
}

b方法(我们希望增强a方法,所以递归调用)

- (void)b {
 [self b];//递归调用
 NSLog(@"b");
}

这样就会导致crash,报错为A类中找不到b这个selector.

原因是当执行[aClass a]的时候,因为交换Imp的原因,则会进入到b方法中.而b中此时的self,是指的A,因为交换仅仅改变Imp,receiver和selector都不会改变,A类中没有b这个方法,当然会crash.

解决方案是:
如果需要在不同的类中交换方法,一定要注意此时的self指的是什么.如果需要调用self没有的方法,那么使用class_addMethod将不存在的方法add进去即可.

在不同类之间实现Method Swizzling

先举个例子:已知一个className为 Car 的类中有一个实例方法 - (void)run:(double)speed , 目前需要Hook该方法对速度小于120才执行run的代码, 按照方法交换的流程, 代码应该是这样的:

#import <objc/runtime.h>

@interface MyCar : NSObject
@end

@implementation MyCar

+ (void)load {
    Class originalClass = NSClassFromString(@"Car");
    Class swizzledClass = [self class];
    SEL originalSelector = NSSelectorFromString(@"run:");
    SEL swizzledSelector = @selector(xxx_run:);
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);

    // 向Car类中新添加一个xxx_run:的方法
    BOOL registerMethod = class_addMethod(originalClass,
                                          swizzledSelector,
                                          method_getImplementation(swizzledMethod),
                                          method_getTypeEncoding(swizzledMethod));
    if (!registerMethod) {
        return;
    }

    // 需要更新swizzledMethod变量,获取当前Car类中xxx_run:的Method指针
    swizzledMethod = class_getInstanceMethod(originalClass, swizzledSelector);
    if (!swizzledMethod) {
        return;
    }

    // 后续流程与之前的一致
    BOOL didAddMethod = class_addMethod(originalClass,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(originalClass,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

- (void)xxx_run:(double)speed {
    if (speed < 120) {
        [self xxx_run:speed];
    }
}

@end

与之前的流程相比,在前面添加了两个逻辑:

  • 利用runtime向目标类 Car 动态添加了一个新的方法,此时 Car 类与 MyCar 类一样具备了 xxx_run: 这个方法, MyCar 的利用价值便结束了;
  • 为了完成后续 Car 类中 run: 与 xxx_run: 的方法交换,此时需要更新swizzledMethod变量为 Car 中的 xxx_run: 方法所对应的Method.
以上所有的逻辑也可以合并简化为以下:
+ (void)load {
    Class originalClass = NSClassFromString(@"Car");
    Class swizzledClass = [self class];
    SEL originalSelector = NSSelectorFromString(@"run:");
    SEL swizzledSelector = @selector(xxx_run:);
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);

    IMP originalIMP = method_getImplementation(originalMethod);
    IMP swizzledIMP = method_getImplementation(swizzledMethod);
    const char *originalType = method_getTypeEncoding(originalMethod);
    const char *swizzledType = method_getTypeEncoding(swizzledMethod);

    class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
    class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}

简化后的代码便与之前使用Category的方式并没有什么差异, 这样代码就很容易覆盖到这两种场景了, 但我们需要明确此时 class_replaceMethod 所完成的工作却是不一样的.

  • 第一个 class_replaceMethod 直接在 Car 类中注册了 xxx_run: 方法, 并且指定的IMP为当前 run: 方法的IMP;
  • 第二个 class_replaceMethod 与之前的逻辑一致, 当 run: 方法是实现在 Car 类或 Car 的父类, 分别执行 method_setImplementation 或 class_addMethod ;

参考资料:http://ju.outofmemory.cn/entry/272003
参考资料:http://www.jianshu.com/p/84cfd554356d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值