Method Swizzling
本文翻译自http://nshipster.com/method-swizzling/
If you could blow up the world with the flick of a switch Would you do it? If you could make everybody poor just so you could be rich Would you do it? If you could watch everybody work while you just lay on your back Would you do it? If you could take all the love without giving any back Would you do it? And so we cannot know ourselves or what we’d really do… With all your power … What would you do? —The Flaming Lips, “The Yeah Yeah Yeah Song (With All Your Power)”
—上面是一首歌.想听的话自行百度…(译者注)
上一周讲的关于 关联的对象的文章,我们开始了探索Objective-C 运行时的黑魔法.这一周,我们进一步来探索,来讨论大概是最具有争议的运行时hack技术:method swizzling
Method swizzling是替换现有selector实现的过程..这是一个可以让Objective-C在运行时替换实际调用的方法成为可能.替换的selectors会映射成为class调度表中的优先函数.
例如,我们想追踪在一个iOS APP中用户打开每个页面的次数.
我们可以在每个view controller里面自己实现viewDidAppear:
方法 里面并添加追踪代码.但是这样会有大量的重复代码. 使用继承也是一种方式,但是这将需要继承UIViewController
,UITableViewController
,UINavigationController
,还有每一个其他的viewcontroller.这样同样会带来大量的重复代码…
幸运的是我们还有另外一种解决途径: method swizzling
(译者注:方法 交换/调整)的category.
下面代码为实现:
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(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:
,一个log语句就会被打印出来.
注入行为到这个view contorller的生命周期,响应事件,视图绘制,基础网络栈对于如何使用method swizzling都是很好的例子.其他还是有很多适合swizzling技术使用的场合,并且使iOS开发者讲变得越来越经验丰富了.
+load vs. +initialize
Swizzling 应该总是完成+load
有两个方法是 Objective-C的每个类在运行时都会自动调用的.
+load
是在类初始化加载完毕时发送.当+initialize
被调用仅仅在程序调用类或者此类实例的第一个方法之前.两个方法都是可选的,不过这两个方法仅仅只有在实现后才会被执行
因为 method swizzling 影响全局状态,所以减少竞争条件的可能性是非常重要的.+load
会保证在类初始化完毕之后加载,提供了一点点改变系统行为的一致性. 相比之下,+initialize
没有在实际执行的时候提供保证,它可能从不会被调用,如果这个类从来都不是由app直接发送消息.
dispatch_once
Swizzling应该总是完成一个dispatch_once
再次,因为swizzling会改变全局状态,所以我们应该打起十二万的精神来预防. 原子性就是这样的一个预防,保证代码只正确的执行一次,即使在不同的线程中. Grand Central Dispatch中的dispatch_once
提供了这两种令人满意的行为.所以成为了initializing singletons(初始化单例)的swizzling标准惯例
Selectors, Methods, & Implementations (选择器,方法和实现)
在Objective-C中,选择器,方法和实现涉及到运行时的特别方面,虽然在正常的交谈中,这两个属于经常可以互换的过程一般就是指的消息发送.
下面是苹果的Objective-C运行时参考中对于每一个的描述 > Selector (typedef struct objc_selector *SEL): Selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped”) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded .
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
}