只要善用Google,網(wǎng)上有很多關(guān)于
Method Swizzling
的Demo,在這里我就不打算貼代碼了,主要介紹下概念,原理,注意事項(xiàng)等等。
如果產(chǎn)品經(jīng)理突然說:"在所有頁面添加統(tǒng)計(jì)功能,也就是用戶進(jìn)入這個(gè)頁面就統(tǒng)計(jì)一次"。我們會想到下面的一些方法:
直接簡單粗暴的在每個(gè)控制器中加入統(tǒng)計(jì),復(fù)制、粘貼、復(fù)制、粘貼...
上面這種方法太Low了,消耗時(shí)間而且以后非常難以維護(hù),會讓后面的開發(fā)人員罵死的。
我們可以使用繼承的方式來解決這個(gè)問題。創(chuàng)建一個(gè)基類,在這個(gè)基類中添加統(tǒng)計(jì)方法,其他類都繼承自這個(gè)基類。
然而,這種方式修改還是很大,而且定制性很差。以后有新人加入之后,都要囑咐其繼承自這個(gè)基類,所以這種方式并不可取。
Category
我們可以為UIViewController
建一個(gè)Category
,然后在所有控制器中引入這個(gè)Category
。當(dāng)然我們也可以添加一個(gè)PCH
文件,然后將這個(gè)Category
添加到PCH
文件中。
Method Swizzling
我們可以使用蘋果的“黑魔法”Method Swizzling
,Method Swizzling
本質(zhì)上就是對IMP
和SEL
進(jìn)行交換。
在 Objective-C
的運(yùn)行時(shí)中,selectors
, methods
, implementations
指代了不同概念,然而我們通常會說在消息發(fā)送過程中,這三個(gè)概念是可以相互轉(zhuǎn)換的。 下面是蘋果 Objective-C Runtime Reference
中的描述:
Selector(typedef struct objc_selector *SEL)
:在運(yùn)行時(shí) Selectors
用來代表一個(gè)方法的名字。Selector
是一個(gè)在運(yùn)行時(shí)被注冊(或映射)的C類型字符串。Selector
由編譯器產(chǎn)生并且在當(dāng)類被加載進(jìn)內(nèi)存時(shí)由運(yùn)行時(shí)自動(dòng)進(jìn)行名字和實(shí)現(xiàn)的映射。
Method(typedef struct objc_method *Method)
:方法是一個(gè)不透明的用來代表一個(gè)方法的定義的類型。
Implementation(typedef id (*IMP)(id, SEL,...))
:這個(gè)數(shù)據(jù)類型指向一個(gè)方法的實(shí)現(xiàn)的最開始的地方。該方法為當(dāng)前CPU架構(gòu)使用標(biāo)準(zhǔn)的C方法調(diào)用來實(shí)現(xiàn)。該方法的第一個(gè)參數(shù)指向調(diào)用方法的自身(即內(nèi)存中類的實(shí)例對象,若是調(diào)用類方法,該指針則是指向元類對象(metaclass
)。第二個(gè)參數(shù)是這個(gè)方法的名字selector
,該方法的真正參數(shù)緊隨其后。理解 selector
, method
, implementation
這三個(gè)概念之間關(guān)系的最好方式是:在運(yùn)行時(shí),類(Class
)維護(hù)了一個(gè)消息分發(fā)列表來解決消息的正確發(fā)送。每一個(gè)消息列表的入口是一個(gè)方法(Method
),這個(gè)方法映射了一對鍵值對,其中鍵值是這個(gè)方法的名字 selector(SEL)
,值是指向這個(gè)方法實(shí)現(xiàn)的函數(shù)指針 implementation(IMP)
。 Method swizzling
修改了類的消息分發(fā)列表使得已經(jīng)存在的 selector
映射了另一個(gè)實(shí)現(xiàn) implementation
,同時(shí)重命名了原生方法的實(shí)現(xiàn)為一個(gè)新的 selector
。
Method Swizzing
是發(fā)生在運(yùn)行時(shí)的,主要用于在運(yùn)行時(shí)將兩個(gè)Method
進(jìn)行交換,我們可以將Method Swizzling
代碼寫到任何地方,但是只有在這段Method Swilzzling
代碼執(zhí)行完畢之后互換才起作用。
在iOS中NSNumber、NSArray、NSDictionary等這些類都是類簇(Class Clusters
),一個(gè)NSArray的實(shí)現(xiàn)可能由多個(gè)類組成。
所以如果想對NSArray進(jìn)行Swizzling,必須獲取到其“真身”進(jìn)行Swizzling,直接對NSArray進(jìn)行操作是無效的。
下面列舉了NSArray和NSDictionary本類的類名,可以通過Runtime函數(shù)取出本類。
類名 | 真身 |
---|---|
NSArray | __NSArrayI |
NSMutableArray | __NSArrayM |
NSDictionary | __NSDictionaryI |
NSMutableDictionary | __NSDictionaryM |
+load
中執(zhí)行dispatch_once
中執(zhí)行+load
中執(zhí)行時(shí),不要調(diào)用[super load]
。如果多次調(diào)用了[super load]
,可能會出現(xiàn)“Swizzle無效”的假象,原理見下圖:要在 Swift 自定義類中使用 Method Swizzling 有兩個(gè)必要條件:
注:對于 Swift 的自定義類,因?yàn)槟J(rèn)并沒有使用 Objective-C 運(yùn)行時(shí),因此也沒有動(dòng)態(tài)派發(fā)的方法列表,所以如果要 Swizzle 的是 Swift 類型的方法的話,是需要將原方法和替換方法都加上 dynamic 標(biāo)記,以指明它們需要使用動(dòng)態(tài)派發(fā)機(jī)制。當(dāng)然類也要繼承自 NSObject。
再注:下面這個(gè)例子使用了 Objective-C 的動(dòng)態(tài)派發(fā),對于 NSObject 的子類(UIViewController)是可以直接使用的,并不是 Swift 中自定義的類,因此沒有加 dynamic 標(biāo)記也是可以的。
區(qū)別 | Objective-C | Swift |
---|---|---|
Runtime 頭文件 | #import <objc/runtime.h> |
不需要 |
Swizzling 調(diào)用處 | load 方法 |
initialize 方法 |
注:load
方法只在 Objective-C 里有,而且不能在 Swift 里重載,不管怎么試都會報(bào)編譯錯(cuò)誤。接下來執(zhí)行 Swizzle 最好的地方就是 initialize
了,這是調(diào)用第一個(gè)方法前的地方。
因?yàn)?Swizzling 會改變?nèi)譅顟B(tài),所以我們需要在運(yùn)行時(shí)采取一些預(yù)防措施。GCD 的dispatch_once
可以保證操作的原子性,確保代碼只被執(zhí)行一次,不管有多少個(gè)線程。
網(wǎng)絡(luò)監(jiān)控的原理,應(yīng)該就是hook NSURLConnection
, NSURLSession
。崩潰收集的原理,應(yīng)該就是hook NSException
。
國外行業(yè)老大
國內(nèi)聽云
國內(nèi)OneAPM
國內(nèi)網(wǎng)易
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。