相信大家对Method-Swizzling
并不陌生,今天我们来聊一聊Method-Swizzling
的一些坑点和优化。
简单使用
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
接着我们在类中调用lg_methodSwizzlingWithClass
交换方法即可
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
- 为避免交换类主动调用
+ load
,重复交换,使用单例设计 - 重写了
+ load
方法,会消耗启动性能,加重启动负担,也可以在+ initialize
方法中交换
代码:
LGPerson
类
@interface LGPerson : NSObject
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation LGPerson
- (void)personInstanceMethod{
NSLog(@"person对象方法:%s",__func__);
}
+ (void)personClassMethod{
NSLog(@"person类方法:%s",__func__);
}
@end
LGStudent
类
@interface LGStudent : LGPerson
- (void)helloword;
@end
@implementation LGStudent
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
- (void)lg_studentInstanceMethod{
NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
[self lg_studentInstanceMethod];
}
@end
viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
LGStudent *s = [[LGStudent alloc] init];
[s helloword];
}
问题一:lg_studentInstanceMethod里面调用lg_studentInstanceMethod会不会产生递归?
答案:不会,在+ load
方法中,lg_studentInstanceMethod
已经和helloword
交换了imp
,里面调用的其实是helloword
方法。
问题二:helloword
方法并没有实现,会出什么问题?
答案:会报错崩溃,找不到
helloword
方法。
下面我们查看一些底层源码,看看问题的产生原因:
class_getInstanceMethod
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
//查找一次imp,参数为LOOKUP_RESOLVER,尝试method resolver
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
//通过sel cls 拿到Method 这是主要方法
return _class_getMethod(cls, sel);
}
_class_getMethod
static Method _class_getMethod(Class cls, SEL sel)
{
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
getMethod_nolock
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
method_t *m = nil;
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
//递归父类查找
while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
cls = cls->superclass;
}
return m;
}
方法慢速查找流程前面的文章详细分析过了。传送门
总结:
方法没有实现的时候,class_getInstanceMethod
会返回空值。
下面看一下交换的方法
method_exchangeImplementations
void method_exchangeImplementations(Method m1, Method m2)
{
//当方法不存在的时候,直接返回。交换失败
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
//imp交换
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
//交换完成后,删除缓存中的数据
flushCaches(nil);
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
与之对应的还有一个class_replaceMethod
class_replaceMethod
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
mutex_locker_t lock(runtimeLock);
//实质上是给类新增一个Method
return addMethod(cls, name, imp, types ?: "", YES);
}
优化
在交换之前,要判断Method是否存在,做对应的处理
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) {
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(cls, oriSEL, imp_implementationWithBlock(^(id self, SEL _cmd){ NSLog(@"%@ has not implementation",_cmd); }), method_getTypeEncoding(oriMethod));
oriMethod = class_getInstanceMethod(cls, oriSEL);
}
if (!swiMethod) {
class_addMethod(cls, swizzledSEL, imp_implementationWithBlock(^(id self, SEL _cmd){ NSLog(@"%@ has not implementation",_cmd); }), method_getTypeEncoding(swiMethod));
swiMethod = class_getInstanceMethod(cls, swizzledSEL);
}
method_exchangeImplementations(oriMethod, swiMethod);
}
判断oriMethod
和swiMethod
是否存在,不存在的话给他们添加一个空的实现。然后再进行交换。