Method Swizzling
先介绍一下背景
背景:项目中使用了第三方的一个播放器,时间显示格式固定不能配置, 时间的格式化写在了NSString的一个分类里面
需求:修改播放器的时间样式
在不修改源代码的前提下,可行方法:
1.直接新建一个分类,使用同名函数"覆盖"掉格式化时间的方法
2.使用Method Swizzling, 将格式化时间的方法交换成自己的实现
方法解析:
使用分类的方法:
利用分类的特性,分类的方法会放在method_list的前面,所以方法查找的时候会先查找到分类的方法,也就实现了"覆盖"之前方法的效果
使用Method Swizzling
使用iOS的运行时,动态的交换两个方法的实现
- 为什么可以交换方法的实现:oc是动态语言,只有在运行程序时,才会根据SEL去查找IMP实现,runtime提供了一套可以用来修改的接口。
使用Method Swizzling注意点:
- 方法交换的代码写在哪里?
写在+(void)load
方法中; 在不人为调用的情况下,每个类加载的时候load方法一定会被调用一次 - 最好使用dispatch_once,保证代码只执行一次
如果交换方法调用2次,则会导致IMP又被交换回来了 - 交换实例方法和类方法的区别
实例方法存放在类对象中;类方法存放在元类中,在元类中以实例方法的形式存在,所以交换类方法时,实际上是交换元类的实例方法。
方法交换
- 用实例方法 交换 实例方法:
#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);
}
}
- 用类方法交换类方法: 类方法存放在元类中,在元类中以实例方法的形式存在,所以交换类方法时,实际上是交换元类的实例方法。
+ (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];
});
}
注意:用类方法交换实例方法 和 用实例方法交换类方法 不常用,旨在帮助加深理解方法交换
- 用类方法 交换 实例方法,注意
self
和metaCls
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
}
- 用实例方法 交换 类方法,注意
self
和metaCls
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);
}
网友评论