Method Swizzling
参考
原文:http://nshipster.com/method-swizzling/
译文:http://nshipster.cn/method-swizzling/
Method swizzling is the process of changing the implementation of an existing selector. It’s a technique made possible by the fact that method invocations in Objective-C can be changed at runtime, by changing how selectors are mapped to underlying functions in a class’s dispatch table.
method swizzling用于改变一个已经存在的selector的实现.这项技术通过改变类的分发列表中selector与函数的映射关系,从而使OC可以在运行时改变方法的调用.
例如:我们想要在一款 iOS app 中追踪每一个视图控制器被用户呈现了几次.这可以通过在每个视图控制器的 viewDidAppear: 方法中添加追踪代码来实现,但这样会产生大量重复的样板代码。继承是另一种可行的方式,但是这要求所有被继承的视图控制器如 UIViewController, UITableViewController, UINavigationController 都在 viewDidAppear:实现追踪代码,这同样会造成很多重复代码。 幸运的是,这里有另外一种可行的方式:利用category实现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);
// When swizzling a class method, use the following:
// 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);
}
@end
+load vs. +initialize
Swizzling should always be done in +load.
There are two methods that are automatically invoked by the Objective-C runtime for each class. +load is sent when the class is initially loaded, while +initialize is called just before the application calls its first method on that class or an instance of that class. Both are optional, and are executed only if the method is implemented.
对于OC的每一个类,有两个方法是由OC运行时自动调用.一个是+load,另一个是+ initialize.+load是当一个类初始装载时被调用,+initialize是应用在第一次调用该类的类方法或实例方法前调用的。这两个方法是可选实现的,仅当方法实现后才会被调用.
Selectors, Methods, & Implementations
In Objective-C, selectors, methods, and implementations refer to particular aspects of the runtime, although in normal conversation, these terms are often used interchangeably to generally refer to the process of message sending.
Here is how each is described in Apple’s Objective-C Runtime Reference:
- 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 .
- Method (typedef struct objc_method *Method): An opaque type that represents a method in a class definition.
- Implementation (typedef id (*IMP)(id, SEL, ...)): This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.
The best way to understand the relationship between these concepts is as follows: a class (Class) maintains a dispatch table to resolve messages sent at runtime; each entry in the table is a method (Method), which keys a particular name, the selector (SEL), to an implementation (IMP), which is a pointer to an underlying C function.
To swizzle a method is to change a class’s dispatch table in order to resolve messages from an existing selector to a different implementation, while aliasing the original method implementation to a new selector.
翻译:在 Objective-C 的运行时中,selectors, methods, implementations 指代了不同概念,但是通常,在消息发送过程中,这三个概念是可以相互转换的。
- Selector:SEL类型.在运行时 Selectors 用来代表一个方法的名字。一个方法的selector就是一个C字符串,并且在运行时被注册(或映射)。Selector由编译器产生并且在当类被加载进内存时由运行时自动进行名字和实现的映射。
- Method: Method类型.一种晦涩的类型,代表类中定义的一个方法.
- Implementation:IMP类型,一个函数指针.指向方法实现的函数首地址.
三者之间的关系的最好理解方式如下:在运行时一个类维护着一个用于解决消息发送的分发列表.列表中的每一个入口都是一个方法(Method),该方法映射了一个键值对,键是selector(SEL类型),值是一个实现(IMP),是一个函数指针,指向实现对应的C函数.
Method Swizzling图示(个人理解,可能并不准确):
方法交换后,SELA映射到IMPB,而SELB映射到IMPA.以后,如果再对对象发送消息SELA,那么函数ImplementationB将会被执行.类似的,对对象发送消息SELB,那么函数ImplementationA将会被执行.
有人可能会觉得Method Swizzling好像也没什么用啊.如果你只是Swizzling一下自己的类的方法确实没什么卵用.但是如果你想要在系统的某个方法执行之前或之后再执行一些你的代码.Method Swizzling就是一个好东西了,你可以写一个自己的方法,然后通过Method Swizzling交换系统的方法.这样以后给对象发送系统的某个消息,那么你的方法就会被执行.
比如UITableView+FDTemplateLayoutCell
第三方库里清除cell缓存的高度的时机,就是Method Swizzling了tableView的系统的reloadData等方法.而使用者却无需关心清除时机,也无需写任何清缓存高度的代码.