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用于改變一個已經(jīng)存在的selector的實(shí)現(xiàn).這項(xiàng)技術(shù)通過改變類的分發(fā)列表中selector與函數(shù)的映射關(guān)系,從而使OC可以在運(yùn)行時改變方法的調(diào)用.
例如:我們想要在一款 iOS app 中追蹤每一個視圖控制器被用戶呈現(xiàn)了幾次.這可以通過在每個視圖控制器的 viewDidAppear: 方法中添加追蹤代碼來實(shí)現(xiàn)蔑歌,但這樣會產(chǎn)生大量重復(fù)的樣板代碼算凿。繼承是另一種可行的方式雄嚣,但是這要求所有被繼承的視圖控制器如 UIViewController, UITableViewController, UINavigationController 都在 viewDidAppear:實(shí)現(xiàn)追蹤代碼菇绵,這同樣會造成很多重復(fù)代碼。 幸運(yùn)的是后雷,這里有另外一種可行的方式:利用category實(shí)現(xiàn)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運(yùn)行時自動調(diào)用.一個是+load,另一個是+ initialize.+load是當(dāng)一個類初始裝載時被調(diào)用,+initialize是應(yīng)用在第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的吠各。這兩個方法是可選實(shí)現(xiàn)的,僅當(dāng)方法實(shí)現(xiàn)后才會被調(diào)用.
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 的運(yùn)行時中臀突,selectors, methods, implementations 指代了不同概念,但是通常,在消息發(fā)送過程中贾漏,這三個概念是可以相互轉(zhuǎn)換的候学。
- Selector:SEL類型.在運(yùn)行時 Selectors 用來代表一個方法的名字。一個方法的selector就是一個C字符串,并且在運(yùn)行時被注冊(或映射)纵散。Selector由編譯器產(chǎn)生并且在當(dāng)類被加載進(jìn)內(nèi)存時由運(yùn)行時自動進(jìn)行名字和實(shí)現(xiàn)的映射梳码。
- Method: Method類型.一種晦澀的類型,代表類中定義的一個方法.
- Implementation:IMP類型,一個函數(shù)指針.指向方法實(shí)現(xiàn)的函數(shù)首地址.
三者之間的關(guān)系的最好理解方式如下:在運(yùn)行時一個類維護(hù)著一個用于解決消息發(fā)送的分發(fā)列表.列表中的每一個入口都是一個方法(Method),該方法映射了一個鍵值對,鍵是selector(SEL類型),值是一個實(shí)現(xiàn)(IMP),是一個函數(shù)指針,指向?qū)崿F(xiàn)對應(yīng)的C函數(shù).
Method Swizzling圖示(個人理解,可能并不準(zhǔn)確):
方法交換后,SELA映射到IMPB,而SELB映射到IMPA.以后,如果再對對象發(fā)送消息SELA,那么函數(shù)ImplementationB將會被執(zhí)行.類似的,對對象發(fā)送消息SELB,那么函數(shù)ImplementationA將會被執(zhí)行.
有人可能會覺得Method Swizzling好像也沒什么用啊.如果你只是Swizzling一下自己的類的方法確實(shí)沒什么卵用.但是如果你想要在系統(tǒng)的某個方法執(zhí)行之前或之后再執(zhí)行一些你的代碼.Method Swizzling就是一個好東西了,你可以寫一個自己的方法,然后通過Method Swizzling交換系統(tǒng)的方法.這樣以后給對象發(fā)送系統(tǒng)的某個消息,那么你的方法就會被執(zhí)行.
比如UITableView+FDTemplateLayoutCell
第三方庫里清除cell緩存的高度的時機(jī),就是Method Swizzling了tableView的系統(tǒng)的reloadData等方法.而使用者卻無需關(guān)心清除時機(jī),也無需寫任何清緩存高度的代碼.