Runtime中Swizz_method方法替换

Runtime中Swizz_method方法替换


Swizzling方法替换 可以优雅的解决许多问题,但是也容易导致不可预料的结果
本文适合新手入门,如有问题请及时纠正,感谢!


参考Objective-C Runtime Reference
主要参数:
- SEL
选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射
- Method
一个代表类定义中一个方法的不明类型
- IMP
实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约


这是方法替换的核心代码

// 给系统方法添加替换方法的实现
// 1.BOOL值为YES,则说明本类中没有系统方法的实现(系统方法的实现在父类中),则通过class_replaceMethod,将系统方法的实现给替换方法
// 2.BOOL值为NO,则说明本类中有系统的实现方法,则通过method_exchangeImplementations将系统的实现与替换方法的实现交换
Method orignalMethod = class_getInstanceMethod(cls, oringalSelector);
Method alternaviMethod = class_getInstanceMethod(cls, alternativeSelector);
BOOL addMethod = class_addMethod(cls, oringalSelector, class_getMethodImplementation(cls, alternativeSelector), method_getTypeEncoding(alternaviMethod));
    if (addMethod) {
        class_replaceMethod(cls, alternativeSelector, class_getMethodImplementation(cls, oringalSelector), method_getTypeEncoding(orignalMethod));
    } else {
        method_exchangeImplementations(orignalMethod, alternaviMethod);
    }

这里存在一个问题,为何需要通过BOOL进行条件判断,能否直接用method_exchangeImplementations做方法的替换?
当然不能,当前类没有系统方法的实现即系统方法实现在父类时,class_getInstanceMethod会返回父类的实现,如果直接调用method_exchangeImplementations,则会替换掉父类的实现,而不是当前类的实现,则不能达到预期的效果


当然如果你觉得这样比较麻烦,你可以参考下这种做法

// 1.直接给当前类添加系统方法的实现和替换方法的实现
// 2.直接调用method_exchangeImplementations进行替换
Method orignalMethod = class_getInstanceMethod(cls, oringalSelector);
Method alternaviMethod = class_getInstanceMethod(cls, alternativeSelector);
// 添加方法实现
class_addMethod(cls, oringalSelector, class_getMethodImplementation(cls, oringalSelector), method_getTypeEncoding(orignalMethod));
class_addMethod(cls, alternativeSelector, class_getMethodImplementation(cls, alternativeSelector), method_getTypeEncoding(alternaviMethod));
// 替换
    method_exchangeImplementations(class_getInstanceMethod(cls, oringalSelector), class_getInstanceMethod(cls, alternativeSelector));

下面从一个简单例子中了解下swizz_method,我们可能会碰到这样的需求,一个带网络请求的按钮,防止按钮的多次点击,就可以通过UIControl分类进行方法替换来优雅的解决这个问题
方法的替换一般是在load方法中进行,确保最先被调用

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 确保方法替换被调用一次,像这样在此处coding方法替换的代码
        __buttonDelay_swizzle(self, @selector(sendAction:to:forEvent:));
    });
}

void __buttonDelay_swizzle(Class cls,SEL oringalSelector) {
    NSString *orignalName = NSStringFromSelector(oringalSelector);
    NSString *alternativeName = [NSString stringWithFormat:@"swizzled_%@",orignalName];
    SEL alternativeSelector = NSSelectorFromString(alternativeName);
    Method orignalMethod = class_getInstanceMethod(cls, oringalSelector);
    Method alternaviMethod = class_getInstanceMethod(cls, alternativeSelector);
    // 添加方法实现
    class_addMethod(cls, oringalSelector, class_getMethodImplementation(cls, oringalSelector), method_getTypeEncoding(orignalMethod));
    class_addMethod(cls, alternativeSelector, class_getMethodImplementation(cls, alternativeSelector), method_getTypeEncoding(alternaviMethod));
    // 替换
    method_exchangeImplementations(class_getInstanceMethod(cls, oringalSelector), class_getInstanceMethod(cls, alternativeSelector));
}

替换方法的实现
// 1.此处的逻辑是如果按钮被点击,则做一个异步延迟,在延迟期间,按钮点击无效
// 2.如果你足够细心, 会发现我在方法中调用了自身[self swizzled_sendAction:action to:target forEvent:event],这里不用担心引起循环,因为此处调用替换的方法其实调用的是系统的方法实现,因为这个方法已经被替换掉了

- (void)swizzled_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([NSStringFromClass(self.class) isEqualToString:@"UIButton"]) {
    //isIngoreEvent bool值,用来标识是否响应按钮点击
        if (self.isIngoreEvent) {
            return;
        } else {
        // buttonClickDelay NSTimeInterval 用来设置按钮点击时间间隔
            if (self.buttonClickDelay > 0) {
                [self setIsIngoreEvent:YES];
                [self performSelector:@selector(ingonreEvent) withObject:nil afterDelay:self.buttonClickDelay];
            }
        }
    }
    [self swizzled_sendAction:action to:target forEvent:event];
}
- (void)ingonreEvent {
    [self setIsIngoreEvent:NO];
}


  • Swizz_method虽然能在运行时实现许多我们的奇思妙想,解决按钮的延迟,自定义navigation滑动手势失效等等很多问题,但是还是可能使得你的程序出现很难定位的BUG,阅读 Objective-C Runtime Reference 和 去理解代码是怎样和为什么这样执行的,努力的用你的理解来消灭你的疑惑
  • 如果你没有足够理由这么做,请在替换方法的实现中调用原始方法,来保证系统的私有Api不被破坏
  • 有兴趣的朋友也可以去Github上研究下JRSwizzle的内部实现
  • 转载请注出处http://blog.csdn.net/zcnfmd/article/details/52672666
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值