Method Swizzling 使用

2020-06-16  本文已影响0人  Ocean_e553

Method Swizzling

先介绍一下背景

背景:项目中使用了第三方的一个播放器,时间显示格式固定不能配置, 时间的格式化写在了NSString的一个分类里面
需求:修改播放器的时间样式

在不修改源代码的前提下,可行方法:

1.直接新建一个分类,使用同名函数"覆盖"掉格式化时间的方法
2.使用Method Swizzling, 将格式化时间的方法交换成自己的实现

方法解析:

使用分类的方法:

利用分类的特性,分类的方法会放在method_list的前面,所以方法查找的时候会先查找到分类的方法,也就实现了"覆盖"之前方法的效果

使用Method Swizzling

使用iOS的运行时,动态的交换两个方法的实现

使用Method Swizzling注意点:

  1. 方法交换的代码写在哪里?
    写在 +(void)load 方法中; 在不人为调用的情况下,每个类加载的时候load方法一定会被调用一次
  2. 最好使用dispatch_once,保证代码只执行一次
    如果交换方法调用2次,则会导致IMP又被交换回来了
  3. 交换实例方法和类方法的区别
    实例方法存放在类对象中;类方法存放在元类中,在元类中以实例方法的形式存在,所以交换类方法时,实际上是交换元类的实例方法。

方法交换

  1. 用实例方法 交换 实例方法:
#import <objc/runtime.h>

+ (void)load {
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
         SEL oriSEL = @selector(logInfo:); // 原方法,实例方法
         SEL swizzleSEL = @selector(de_logInfo:); //  新的方法,实例方法
         [self de_methodSwizzlingWithClass:self oriSEL:oriSEL swizzledSEL:swizzleSEL];
     });
}


#pragma mark - method swizzling
 + (void)de_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
             
     Method oriMethod = class_getInstanceMethod(cls, oriSEL);
     Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);

     BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
     if (didAddMethod) {
         class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
     }else{
         method_exchangeImplementations(oriMethod, swiMethod);
     }
 }

  1. 用类方法交换类方法: 类方法存放在元类中,在元类中以实例方法的形式存在,所以交换类方法时,实际上是交换元类的实例方法。
+ (void)load {
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
         SEL oriSEL = @selector(logInfo:); // 原方法,类方法
         SEL swizzleSEL = @selector(de_logInfo:);  // 新方法,类方法
         Class metaCls = object_getClass(self);    // 获取元类,交换元类的实例方法
         [self de_methodSwizzlingWithClass:metaCls oriSEL:oriSEL swizzledSEL:swizzleSEL];
     });
}

注意:用类方法交换实例方法 和 用实例方法交换类方法 不常用,旨在帮助加深理解方法交换

  1. 用类方法 交换 实例方法,注意 selfmetaCls
 SEL oriInsSEL = @selector(instanHello); // 原方法,实例方法
 SEL swizzleClsSEL = @selector(de_hello); // 新方法,类方法
 Class metaCls = object_getClass(self); // 元类
 Method oriInsMethod = class_getInstanceMethod(self, oriInsSEL); // 获取实例方法
 Method swiClsMethod = class_getInstanceMethod(metaCls, swizzleClsSEL); // 获取类方法
 BOOL didAddMethod = class_addMethod(self, oriInsSEL, method_getImplementation(swiClsMethod), method_getTypeEncoding(swiClsMethod)); // 尝试添加 `实例方法` oriInsSEL -> swiClsMethod.IMP
 if (didAddMethod) {
     class_replaceMethod(metaCls, swizzleClsSEL, method_getImplementation(oriInsMethod), method_getTypeEncoding(oriInsMethod)); // 如果上一步方法添加成功,则只需要替换掉 新方法的实现 即可
 }else{
     method_exchangeImplementations(oriInsMethod, swiClsMethod); // 交换两个方法的IMP
 }
  1. 用实例方法 交换 类方法,注意 selfmetaCls
 SEL oriClsSEL = @selector(hello); // 原方法,类方法
 SEL swizzleInsSEL = @selector(de_insHello); // 新方法,实例方法
 Class metaCls = object_getClass(self); // 元类
 Method oriClsMethod = class_getInstanceMethod(metaCls, oriClsSEL); // 原方法,类方法
 Method swiInsMethod = class_getInstanceMethod(self, swizzleInsSEL); // 新方法,实例方法
 BOOL didAddedMethod = class_addMethod(metaCls, oriClsSEL, method_getImplementation(swiInsMethod), method_getTypeEncoding(swiInsMethod)); // 尝试添加 `类方法` oriClsSEL -> swizzleInsSEL.IMP
 if (didAddedMethod) {
     class_replaceMethod(self, swizzleInsSEL, method_getImplementation(oriClsMethod), method_getTypeEncoding(oriClsMethod));
 } else {
     method_exchangeImplementations(oriClsMethod, swiInsMethod);
 }
上一篇 下一篇

猜你喜欢

热点阅读