<p align="center">
<img src ="https://raw.githubusercontent.com/DotzuX/Notes/master/logo.jpeg"/>;
</p>git
只要善用Google,網上有不少關於
Method Swizzling
的Demo,在這裏我就不打算貼代碼了,主要介紹下概念,原理,注意事項等等。github
若是產品經理忽然說:"在全部頁面添加統計功能,也就是用戶進入這個頁面就統計一次"。咱們會想到下面的一些方法:設計模式
直接簡單粗暴的在每一個控制器中加入統計,複製、粘貼、複製、粘貼...
上面這種方法太Low了,消耗時間並且之後很是難以維護,會讓後面的開發人員罵死的。網絡
咱們可使用繼承的方式來解決這個問題。建立一個基類,在這個基類中添加統計方法,其餘類都繼承自這個基類。架構
然而,這種方式修改仍是很大,並且定製性不好。之後有新人加入以後,都要囑咐其繼承自這個基類,因此這種方式並不可取。ide
Category
咱們能夠爲UIViewController
建一個Category
,而後在全部控制器中引入這個Category
。固然咱們也能夠添加一個PCH
文件,而後將這個Category
添加到PCH
文件中。函數
Method Swizzling
咱們可使用蘋果的「黑魔法」Method Swizzling
,Method Swizzling
本質上就是對IMP
和SEL
進行交換。性能
在 Objective-C
的運行時中,selectors
, methods
, implementations
指代了不一樣概念,然而咱們一般會說在消息發送過程當中,這三個概念是能夠相互轉換的。 下面是蘋果 Objective-C Runtime Reference
中的描述:spa
Selector(typedef struct objc_selector *SEL)
:在運行時 Selectors
用來表明一個方法的名字。Selector
是一個在運行時被註冊(或映射)的C類型字符串。Selector
由編譯器產生而且在當類被加載進內存時由運行時自動進行名字和實現的映射。.net
Method(typedef struct objc_method *Method)
:方法是一個不透明的用來表明一個方法的定義的類型。
Implementation(typedef id (*IMP)(id, SEL,...))
:這個數據類型指向一個方法的實現的最開始的地方。該方法爲當前CPU架構使用標準的C方法調用來實現。該方法的第一個參數指向調用方法的自身(即內存中類的實例對象,如果調用類方法,該指針則是指向元類對象(metaclass
)。第二個參數是這個方法的名字selector
,該方法的真正參數緊隨其後。理解 selector
, method
, implementation
這三個概念之間關係的最好方式是:在運行時,類(Class
)維護了一個消息分發列表來解決消息的正確發送。每個消息列表的入口是一個方法(Method
),這個方法映射了一對鍵值對,其中鍵值是這個方法的名字 selector(SEL)
,值是指向這個方法實現的函數指針 implementation(IMP)
。 Method swizzling
修改了類的消息分發列表使得已經存在的 selector
映射了另外一個實現 implementation
,同時重命名了原生方法的實現爲一個新的 selector
。
Method Swizzing
是發生在運行時的,主要用於在運行時將兩個Method
進行交換,咱們能夠將Method Swizzling
代碼寫到任何地方,可是隻有在這段Method Swilzzling
代碼執行完畢以後互換才起做用。
在iOS中NSNumber、NSArray、NSDictionary等這些類都是類簇(Class Clusters
),一個NSArray的實現可能由多個類組成。
因此若是想對NSArray進行Swizzling,必須獲取到其「真身」進行Swizzling,直接對NSArray進行操做是無效的。
下面列舉了NSArray和NSDictionary本類的類名,能夠經過Runtime函數取出本類。
類名 | 真身 |
---|---|
NSArray | __NSArrayI |
NSMutableArray | __NSArrayM |
NSDictionary | __NSDictionaryI |
NSMutableDictionary | __NSDictionaryM |
+load
中執行dispatch_once
中執行+load
中執行時,不要調用[super load]
。若是屢次調用了[super load]
,可能會出現「Swizzle無效」的假象,原理見下圖:要在 Swift 自定義類中使用 Method Swizzling 有兩個必要條件:
注:對於 Swift 的自定義類,由於默認並無使用 Objective-C 運行時,所以也沒有動態派發的方法列表,因此若是要 Swizzle 的是 Swift 類型的方法的話,是須要將原方法和替換方法都加上 dynamic 標記,以指明它們須要使用動態派發機制。固然類也要繼承自 NSObject。
再注:下面這個例子使用了 Objective-C 的動態派發,對於 NSObject 的子類(UIViewController)是能夠直接使用的,並非 Swift 中自定義的類,所以沒有加 dynamic 標記也是能夠的。
區別 | Objective-C | Swift |
---|---|---|
Runtime 頭文件 | #import <objc/runtime.h> |
不須要 |
Swizzling 調用處 | load 方法 |
initialize 方法 |
注:load
方法只在 Objective-C 裏有,並且不能在 Swift 裏重載,無論怎麼試都會報編譯錯誤。接下來執行 Swizzle 最好的地方就是 initialize
了,這是調用第一個方法前的地方。
由於 Swizzling 會改變全局狀態,因此咱們須要在運行時採起一些預防措施。GCD 的dispatch_once
能夠保證操做的原子性,確保代碼只被執行一次,無論有多少個線程。
網絡監控的原理,應該就是hook NSURLConnection
, NSURLSession
。崩潰收集的原理,應該就是hook NSException
。
國外行業老大
國內聽雲
國內OneAPM
國內網易