Runtime 又叫運行時,iOS 內(nèi)部的核心之一,底層的 為C 語言 寫的API,底層都是基于它來實現(xiàn)的筏勒。在沒有一個類的實現(xiàn)源碼的情況下男公,想改變其中一個方法的實現(xiàn)千所,除了繼承它重寫、和借助類別重名方法暴力搶先之外吮播,還有更加靈活的方法嗎鹰服?那就是Method Swizzling病瞳,利用運行時優(yōu)雅地實現(xiàn)。
一悲酷、說到Method Swizzling先提到幾個概念:
SEL:它是selector在 Objc 中的表示(Swift 中是 Selector 類)套菜。selector 是方法選擇器,其實作用就和名字一樣设易,日常生活中逗柴,我們通過人名辨別誰是誰,注意 Objc 在相同的類中不會有命名相同的兩個方法顿肺。selector 對方法名進行包裝戏溺,以便找到對應(yīng)的方法實現(xiàn)。它的數(shù)據(jù)結(jié)構(gòu)是:
typedefstruct objc_selector *SEL;
IMP
IMP實際上是一個函數(shù)指針屠尊,指向方法實現(xiàn)的地址旷祸。
其定義如下:
id (*IMP)(id, SEL,...)
Method:Method 代表類中某個方法的類型
typedefstruct objc_method *Method;struct objc_method {
SEL method_name
OBJC2_UNAVAILABLE;char *method_types
OBJC2_UNAVAILABLE;
IMP method_imp
OBJC2_UNAVAILABLE;}
objc_method存儲了方法名,方法類型和方法實現(xiàn):
方法名類型為SEL
方法類型method_types是個 char 指針讼昆,存儲方法的參數(shù)類型和返回值類型
method_imp指向了方法的實現(xiàn)托享,本質(zhì)是一個函數(shù)指針
二、Method Swizzling原理
Method Swizzing是發(fā)生在運行時的,主要用于在運行時將兩個Method進行交換闰围,我們可以將Method Swizzling代碼寫到任何地方赃绊,但是只有在這段Method Swilzzling代碼執(zhí)行完畢之后互換才起作用。
而且Method Swizzling也是iOS中AOP(面相切面編程)的一種實現(xiàn)方式羡榴,我們可以利用蘋果這一特性來實現(xiàn)AOP編程碧查。
首先,讓我們通過兩張圖片來了解一下Method Swizzling的實現(xiàn)原理
圖一
圖二
上面圖一中selector2原本對應(yīng)著IMP2校仑,但是為了更方便的實現(xiàn)特定業(yè)務(wù)需求么夫,我們在圖二中添加了selector3和IMP3,并且讓selector2指向了IMP3肤视,而selector3則指向了IMP2,這樣就實現(xiàn)了“方法互換”涉枫。
在OC語言的runtime特性中邢滑,調(diào)用一個對象的方法就是給這個對象發(fā)送消息。是通過查找接收消息對象的方法列表愿汰,從方法列表中查找對應(yīng)的SEL困后,這個SEL對應(yīng)著一個IMP(一個IMP可以對應(yīng)多個SEL),通過這個IMP找到對應(yīng)的方法調(diào)用衬廷。
在每個類中都有一個Dispatch Table摇予,這個Dispatch Table本質(zhì)是將類中的SEL和IMP(可以理解為函數(shù)指針)進行對應(yīng)。而我們的Method Swizzling就是對這個table進行了操作吗跋,讓SEL對應(yīng)另一個IMP侧戴。
三、Method Swizzling實現(xiàn)
在實現(xiàn)Method Swizzling時跌宛,核心代碼主要就是一個runtime的C語言API:
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)
__OSX_AVAILABLE_STARTING(__MAC_10_5,?__IPHONE_2_0);
而 Method Swizzling 可以交換兩個方法的IMP酗宋。你可以重寫某個方法而不用繼承,同時還可以調(diào)用原先的實現(xiàn)疆拘。通常的做法是在category中添加一個方法(當(dāng)然也可以是一個全新的class)蜕猫。可以通過method_exchangeImplementations這個運行時方法來交換實現(xiàn)哎迄。來看一個demo回右,這個demo演示了如何重寫addObject:方法來紀錄每一個新添加的對象。
#import <objc/runtime.h>
@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end
@implementation NSMutableArray (LoggingAddObject)
+ (void)load {
Method addobject = class_getInstanceMethod(self, @selector(addObject:));
Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
method_exchangeImplementations(addObject, logAddObject);
}
- (void)logAddObject:(id)aobject {
[self logAddObject:aObject];
NSLog(@"Added object %@ to array %@", aObject, self);
}
@end
我們把方法交換放到了load中漱挚,這個方法只會被調(diào)用一次翔烁,而且是運行時載入。如果指向臨時用一下棱烂,可以放到別的地方租漂。注意到一個很明顯的遞歸調(diào)用logAddObject:。這也是Method Swizzling容易把我們搞混的地方,因為我們已經(jīng)交換了方法的實現(xiàn)哩治,所以其實調(diào)用的是addObject: