Runtime进阶一:Method Swizzling
本文详解通过runtime来替换两个方法的实现,对runtime不熟悉的小白可以先跳转以下链接了解一下
在OC中Runtime的实际应用场景比较常用的大概有以下这么几种:
一:Method Swizzling (替换两个方法的实现)
二:实现给分类添加属性
三:实现字典和模型的自动转换
四:当属性过多时,通过runtime便捷归解档
五:JSPatch替换已有的OC方法实现
本文详解第一条具体方式,后面几点会在接下来有空的时候慢慢补充,想看的或者互相交流的可以关注我的博客。
Method Swizzling
常规写法
+ (void)load {
//由于 swizzling 改变了全局的状态,所以需要确保在运行时,我们采用的预防措施是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once 满足了这些需求,所以,Method Swizzling 应该在 dispatch_once 中完成
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {//添加到自动释放池,优化内存
//这里就是随便举了个例子替换button点击后发送消息的方法
Method systemMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
SEL sysSEL = @selector(sendAction:to:forEvent:);
Method customMethod = class_getInstanceMethod(self, @selector(custom_sendAction:to:forEvent:));
SEL customSEL = @selector(custom_sendAction:to:forEvent:);
//添加方法 语法:BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 若添加成功则返回No
// cls:被添加方法的类 name:被添加方法方法名 imp:被添加方法的实现函数 types:被添加方法的实现函数的返回值类型和参数类型的字符串
BOOL didAddMethod = class_addMethod(self, sysSEL, method_getImplementation(customMethod), method_getTypeEncoding(customMethod));
//如果系统中该方法已经存在了,则替换系统的方法 语法:IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)
if (didAddMethod) {
class_replaceMethod(self, customSEL, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
method_exchangeImplementations(systemMethod, customMethod);
}
}
});
}
- (void)custom_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
}
但是这么写的话,同一个类里有多个方法需要替换就很不方便。封装一下之后的写法如下。懒得看的可以直接下载demo。
demo链接
首先是封装一个公共的替换两个方法的类
#import "NSObject+ZYTSwizz.h"
@implementation NSObject (ZYTSwizz)
/**
sysSelector:原方法
mySelector:将要替换的方法
*/
- (void)swizzMethodWithSysSelector:(SEL)sysSelector swizzMySelector:(SEL)mySelector {
//调用此方法的类
Class class = [self class];
Method systemMethod = class_getInstanceMethod(class, sysSelector);
Method myMethod = class_getInstanceMethod(class, mySelector);
//didAddMethod 为NO,表示添加成功
BOOL didAddMethod = class_addMethod(class, sysSelector, method_getImplementation(myMethod), method_getTypeEncoding(myMethod));
if (didAddMethod) {
class_replaceMethod(class, mySelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else {
method_exchangeImplementations(systemMethod, myMethod);
}
}
然后这样调用
#import "NSMutableDictionary+ZYTCategory.h"
#import "NSObject+ZYTSwizz.h"
@implementation NSMutableDictionary (ZYTCategory)
+ (void)load {
[super load];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
//类名后面跟的M表示可变字典,跟I表示不可变字典,跟0表示刚实例化还没有赋值
[objc_getClass("__NSDictionaryM") swizzMethodWithSysSelector:@selector(setObject:forKey:) swizzMySelector:@selector(safe_setObject:forKey:)];
}
});
}
/** 防止当object或key为空时crash */
- (void)safe_setObject:(id)obj forKey:(NSString *)key {
if (obj == nil || key == nil) {
return;
}
[self safe_setObject:obj forKey:key];
}