理解Method Swizzling是学习runtime机制的一个很好的机会。在此很少作整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文。安全
Method Swizzling是改变一个selector的实际实现的技术。经过这一技术,咱们能够在运行时经过修改类的分发表中selector对应的函数,来修改方法的实现。架构
例如,咱们想跟踪在程序中每个view controller展现给用户的次数:固然,咱们能够在每一个view controller的viewDidAppear中添加跟踪代码;可是这太过麻烦,须要在每一个view controller中写重复的代码。建立一个子类多是一种实现方式,但须要同时建立UIViewController, UITableViewController, UINavigationController及其它UIKit中view controller的子类,这一样会产生许多重复的代码。并发
这种状况下,咱们就可使用Method Swizzling,如在代码所示:框架
#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
在这里,咱们经过method swizzling修改了UIViewController的@selector(viewWillAppear:)对应的函数指针,使其实现指向了咱们自定义的xxx_viewWillAppear的实现。这样,当UIViewController及其子类的对象调用viewWillAppear时,都会打印一条日志信息。函数
上面的例子很好地展现了使用method swizzling来一个类中注入一些咱们新的操做。固然,还有许多场景可使用method swizzling,在此很少举例。在此咱们说说使用method swizzling须要注意的一些问题:学习
在Objective-C中,运行时会自动调用每一个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法以前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。因为method swizzling会影响到类的全局状态,所以要尽可能避免在并发处理中出现竞争的状况。+load能保证在类的初始化过程当中被加载,并保证这种改变应用级别的行为的一致性。相比之下,+initialize在其执行时不提供这种保证—事实上,若是在应用中没为给这个类发送消息,则它可能永远不会被调用。spa
与上面相同,由于swizzling会改变全局状态,因此咱们须要在运行时采起一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,无论有多少个线程。GCD的dispatch_once能够确保这种行为,咱们应该将其做为method swizzling的最佳实践。线程
在Objective-C中,选择器(selector)、方法(method)和实现(implementation)是运行时中一个特殊点,虽然在通常状况下,这些术语更多的是用在消息发送的过程描述中。翻译
如下是Objective-C Runtime Reference中的对这几个术语一些描述:指针
理解这几个术语之间的关系最好的方式是:一个类维护一个运行时可接收的消息分发表;分发表中的每一个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个实现(IMP),即指向底层C函数的指针。
为了swizzle一个方法,咱们能够在分发表中将一个方法的现有的选择器映射到不一样的实现,而将该选择器对应的原始实现关联到一个新的选择器中。
咱们回过头来看看前面新的方法的实现代码:
- (void)xxx_viewWillAppear:(BOOL)animated { [self xxx_viewWillAppear:animated]; NSLog(@"viewWillAppear: %@", NSStringFromClass([self class])); }
咋看上去是会致使无限循环的。但使人惊奇的是,并无出现这种状况。在swizzling的过程当中,方法中的[self xxx_viewWillAppear:animated]已经被从新指定到UIViewController类的-viewWillAppear:中。在这种状况下,不会产生无限循环。不过若是咱们调用的是[self viewWillAppear:animated],则会产生无限循环,由于这个方法的实如今运行时已经被从新指定为xxx_viewWillAppear:了。
Swizzling一般被称做是一种黑魔法,容易产生不可预知的行为和没法预见的后果。虽然它不是最安全的,但若是听从如下几点预防措施的话,仍是比较安全的: