runtime中的Method Swizzling(AOP)
一、動態的為類注入新的方法
使用場景:攔截系統方法 eg: viewVillAppear,viewDidLoad,imageNamed....
使用到的方法
- 添加方法
/**
* cls 被添加方法所在的類
* name 添加的方法名字
* IMP 實現這個方法的函數
* types 一個定義函數返回類型和參數的類型的字符串 (常用method_getTypeEncoding(method)獲取)
*/
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
- 替換方法的實現
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
- 交換兩個方法的實現
method_exchangeImplementations(Method m1, Method m2)
- 獲得某個類的類方法
Method class_getClassMethod(Class cls , SEL name)
- 獲得某個類的實例對象方法
Method class_getInstanceMethod(Class cls , SEL name)
其本質無非就是改變方法選擇器指針的指向(改變指針的地址)
eg:AOP中攔截一個方法的實現
// originalSelector 原有方法
// swizzledSelector 新的方法
+ (void)swizzledMethod:(SEL)originalSelector and:(SEL)swizzledSelector
{
// 獲取當前類
Class currentClass = [self class];
// 當前方法選擇器指向的實例方法
Method originalMethod = class_getInstanceMethod(currentClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(currentClass, swizzledSelector);
// 為當前類添加一個方法(如果當前類中已經存在originalSelector,返回false,否則返回true)
BOOL didAddMethod =
class_addMethod(currentClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// 方法添加成功
if (didAddMethod) {
class_replaceMethod(currentClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {// 如果當前類中已經存在originalSelector 直接交換兩個方法的實現
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
選擇器、方法與實現
在Objective-C中,選擇器(selector)、方法(method)和實現(implementation)是運行時中一個特殊點,雖然在一般情況下,這些術語更多的是用在消息發送的過程描述中。
以下是Objective-C Runtime Reference中的對這幾個術語一些描述:
Selector(typedef struct objc_selector *SEL):用于在運行時中表示一個方法的名稱。一個方法選擇器是一個C字符串,它是在Objective-C運行時被注冊的。選擇器由編譯器生成,并且在類被加載時由運行時自動做映射操作。
Method(typedef struct objc_method Method):在類定義中表示方法的類型
Implementation(typedef id (IMP)(id, SEL, …)):這是一個指針類型,指向方法實現函數的開始位置。這個函數使用為當前CPU架構實現的標準C調用規范。每一個參數是指向對象自身的指針(self),第二個參數是方法選擇器。然后是方法的實際參數。
理解這幾個術語之間的關系最好的方式是:一個類維護一個運行時可接收的消息分發表;分發表中的每個入口是一個方法(Method),其中key是一個特定名稱,即選擇器(SEL),其對應一個實現(IMP),即指向底層C函數的指針。
為了swizzle一個方法,我們可以在分發表中將一個方法的現有的選擇器映射到不同的實現,而將該選擇器對應的原始實現關聯到一個新的選擇器中。
注意細節
Swizzling應該總是在+load中執行
在Objective-C中,運行時會自動調用每個類的兩個方法。+load會在類初始加載時調用,+initialize會在第一次調用類的類方法或實例方法之前被調用。這兩個方法是可選的,且只有在實現了它們時才會被調用。由于method swizzling會影響到類的全局狀態,因此要盡量避免在并發處理中出現競爭的情況。+load能保證在類的初始化過程中被加載,并保證這種改變應用級別的行為的一致性。相比之下,+initialize在其執行時不提供這種保證—事實上,如果在應用中沒為給這個類發送消息,則它可能永遠不會被調用。
Swizzling應該總是在dispatch_once中執行
與上面相同,因為swizzling會改變全局狀態,所以我們需要在運行時采取一些預防措施。原子性就是這樣一種措施,它確保代碼只被執行一次,不管有多少個線程。GCD的dispatch_once可以確保這種行為,我們應該將其作為method swizzling的最佳實踐。