Method swizzling 就是改变现有selector 所关联的方法的实现。底层原理为,利用runTime更改类底层方法调度表的映射,在完成映射更改后,这样调用既有的selector就会改为调用更改后的方法实现。从而达到方法替换的目的。
在开发中合理的利用Method swizzling可以为我们减少很多冗余的工作。
假设我们想追踪用户在每一个视图控制器的停留时间,不要以为这是一项不必要的工作,恰恰相反这是一批非常有意义的数据(第三方平台友盟为我们提供了相应的后台)。
一个并不复杂的需求,但是我们需要在每一个视图控制器的生命周期函数中加入同样的追踪代码,成吨的冗余代码将会出现在我们的代码中,这并不是一个很好的实现。庆幸的是Method Swizzling可以很好的帮我们解决个麻烦,具体代码如下:
#import
<objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 如果swizzle一个类方法,用下面的方式:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
现在任何一个UIViewController或是他的子类的实例调用到viewWillAppear: 都会去调用xxx_viewWillAppear:的实现,我们也就可以在这里集中处理控制器展示时间的代码。
添加代码到视图控制器生命周期,替换三方库中代码实现,更改底层网络设置等都是很好的应用method swizzling的例子。恰当的使用method swizzling使开发更加高效,但method swizzling是类似于开发中黑魔法的存在,过多的使用会导致代码的晦涩难懂。
无论什么情况下,什么原因使用method swizzling,下面的一些原则是必须要遵守的
Swizzling 应该写在
+load
.OC中有两个方法会被系统自动调用
+load:在程序启动时加载到该类的时候就会被调用。
+initialize:在程序第一次调用该类 类方法或实例方法时调用
method swizzling是在系统全局产生影响的,所以我们应该在尽可能早的地方实现它。+load在系统加载类的时候就获得了执行。
dispatch_once
method swizzling应该总是被鞋子一个dispatch_once的block中
method swizzling是在系统全局产生影响的,我们必须确保其中的代码只被执行一次哪怕是在不同的线程之中,dispatch_once为我们很好的提供了这个保障。
Invoking
_cmd
如下的代码可能会让你感到困惑,这样不会导致死循环么
- (void)xxx_viewWillAppear:(BOOL)animated
{
[self xxx_viewWillAppear:animated];
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@",
self);
}
是的不会的,事实上正如前面所说,在+load中我们已经完成了Swizzling,当我们调用viewWillAppear:,实际执行的是xxx_viewWillAppear:中的代码,同样的当我们在调用xxx_viewWillAppear:实际执行的是viewWillAppear:中的代码,是不是一种非常微妙的逻辑!!!~