iOS開發之 Method Swizzling 深入淺出
<img src ="https://raw.githubusercontent.com/DotzuX/Notes/master/logo.jpeg"/>
</p>
iOS開發之 Method Swizzling 深入淺出
只要善用Google,網上有很多關於
Method Swizzling
的Demo,在這裏我就不打算貼代碼了,主要介紹下概念,原理,註意事項等等。
開發需求
如果產品經理突然說:"在所有頁面添加統計功能,也就是用戶進入這個頁面就統計一次"。我們會想到下面的一些方法:
- 手動添加
直接簡單粗暴的在每個控制器中加入統計,復制、粘貼、復制、粘貼...
上面這種方法太Low了,消耗時間而且以後非常難以維護,會讓後面的開發人員罵死的。
- 繼承
我們可以使用繼承的方式來解決這個問題。創建一個基類,在這個基類中添加統計方法,其他類都繼承自這個基類。
然而,這種方式修改還是很大,而且定制性很差。以後有新人加入之後,都要囑咐其繼承自這個基類,所以這種方式並不可取。
Category
我們可以為UIViewController
建一個Category
,然後在所有控制器中引入這個Category
。當然我們也可以添加一個PCH
文件,然後將這個Category
添加到PCH
文件中。
Method Swizzling
我們可以使用蘋果的“黑魔法”Method Swizzling
,Method Swizzling
本質上就是對IMP
和SEL
進行交換。
先了解幾個概念
Selectors, Methods, & Implementations
在 Objective-C
的運行時中,selectors
, methods
, implementations
指代了不同概念,然而我們通常會說在消息發送過程中,這三個概念是可以相互轉換的。 下面是蘋果 Objective-C Runtime Reference
中的描述:
-
Selector(typedef struct objc_selector *SEL)
:在運行時Selectors
Selector
是一個在運行時被註冊(或映射)的C類型字符串。Selector
由編譯器產生並且在當類被加載進內存時由運行時自動進行名字和實現的映射。 -
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 Swizzling原理
Method Swizzing
是發生在運行時的,主要用於在運行時將兩個Method
進行交換,我們可以將Method Swizzling
代碼寫到任何地方,但是只有在這段Method Swilzzling
代碼執行完畢之後互換才起作用。
Method Swizzling 使用註意
類簇設計模式
在iOS中NSNumber、NSArray、NSDictionary等這些類都是類簇(Class Clusters
),一個NSArray的實現可能由多個類組成。
所以如果想對NSArray進行Swizzling,必須獲取到其“真身”進行Swizzling,直接對NSArray進行操作是無效的。
下面列舉了NSArray和NSDictionary本類的類名,可以通過Runtime函數取出本類。
類名 | 真身 |
---|---|
NSArray | __NSArrayI |
NSMutableArray | __NSArrayM |
NSDictionary | __NSDictionaryI |
NSMutableDictionary | __NSDictionaryM |
註意要點
- Swizzling應該總在
+load
中執行 - Swizzling應該總是在
dispatch_once
中執行 - Swizzling在
+load
中執行時,不要調用[super load]
。如果多次調用了[super load]
,可能會出現“Swizzle無效”的假象,原理見下圖:
Swift 自定義類中使用 Method Swizzling
要在 Swift 自定義類中使用 Method Swizzling 有兩個必要條件:
- 包含 Swizzle 方法的類需要繼承自 NSObject
- 需要 Swizzle 的方法必須有動態屬性(dynamic attribute)
註:對於 Swift 的自定義類,因為默認並沒有使用 Objective-C 運行時,因此也沒有動態派發的方法列表,所以如果要 Swizzle 的是 Swift 類型的方法的話,是需要將原方法和替換方法都加上 dynamic 標記,以指明它們需要使用動態派發機制。當然類也要繼承自 NSObject。
再註:下面這個例子使用了 Objective-C 的動態派發,對於 NSObject 的子類(UIViewController)是可以直接使用的,並不是 Swift 中自定義的類,因此沒有加 dynamic 標記也是可以的。
Method Swizzling 中 Objective-C 與 Swift 的異同
區別 | Objective-C | Swift |
---|---|---|
Runtime 頭文件 | #import <objc/runtime.h> |
不需要 |
Swizzling 調用處 | load 方法 |
initialize 方法 |
註:load
方法只在 Objective-C 裏有,而且不能在 Swift 裏重載,不管怎麽試都會報編譯錯誤。接下來執行 Swizzle 最好的地方就是 initialize
了,這是調用第一個方法前的地方。
因為 Swizzling 會改變全局狀態,所以我們需要在運行時采取一些預防措施。GCD 的dispatch_once
可以保證操作的原子性,確保代碼只被執行一次,不管有多少個線程。
Method Swizzling 實際應用
APM(應用性能管理)
網絡監控的原理,應該就是hook NSURLConnection
, NSURLSession
。崩潰收集的原理,應該就是hook NSException
。
- https://newrelic.com/
國外行業老大
- http://www.tingyun.com/
國內聽雲
- http://www.oneapm.com/
國內OneAPM
- http://apm.netease.com/
國內網易
國外資料
iOS開發之 Method Swizzling 深入淺出