iOS開發之 Method Swizzling 深刻淺出

<p align="center">
<img src ="https://raw.githubusercontent.com/DotzuX/Notes/master/logo.jpeg"/>;
</p>git

iOS開發之 Method Swizzling 深刻淺出

只要善用Google,網上有不少關於Method Swizzling的Demo,在這裏我就不打算貼代碼了,主要介紹下概念,原理,注意事項等等。github

開發需求

若是產品經理忽然說:"在全部頁面添加統計功能,也就是用戶進入這個頁面就統計一次"。咱們會想到下面的一些方法:設計模式

  • 手動添加

直接簡單粗暴的在每一個控制器中加入統計,複製、粘貼、複製、粘貼...
上面這種方法太Low了,消耗時間並且之後很是難以維護,會讓後面的開發人員罵死的。網絡

  • 繼承

咱們可使用繼承的方式來解決這個問題。建立一個基類,在這個基類中添加統計方法,其餘類都繼承自這個基類。架構

然而,這種方式修改仍是很大,並且定製性不好。之後有新人加入以後,都要囑咐其繼承自這個基類,因此這種方式並不可取。ide

  • Category

咱們能夠爲UIViewController建一個Category,而後在全部控制器中引入這個Category。固然咱們也能夠添加一個PCH文件,而後將這個Category添加到PCH文件中。函數

  • Method Swizzling

咱們可使用蘋果的「黑魔法」Method SwizzlingMethod Swizzling本質上就是對IMPSEL進行交換。性能

先了解幾個概念

Selectors, Methods, & Implementations

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 Swizzling原理

Method Swizzing是發生在運行時的,主要用於在運行時將兩個Method進行交換,咱們能夠將Method Swizzling代碼寫到任何地方,可是隻有在這段Method Swilzzling代碼執行完畢以後互換才起做用。

iOS開發之 Method Swizzling 深刻淺出

iOS開發之 Method Swizzling 深刻淺出

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無效」的假象,原理見下圖:

iOS開發之 Method Swizzling 深刻淺出

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 &lt;objc/runtime.h&gt; 不須要
Swizzling 調用處 load 方法 initialize 方法

注:load 方法只在 Objective-C 裏有,並且不能在 Swift 裏重載,無論怎麼試都會報編譯錯誤。接下來執行 Swizzle 最好的地方就是 initialize了,這是調用第一個方法前的地方。

由於 Swizzling 會改變全局狀態,因此咱們須要在運行時採起一些預防措施。GCD 的dispatch_once 能夠保證操做的原子性,確保代碼只被執行一次,無論有多少個線程。

Method Swizzling 實際應用

APM(應用性能管理)

網絡監控的原理,應該就是hook NSURLConnection , NSURLSession。崩潰收集的原理,應該就是hook NSException

國外資料

相關文章
相關標籤/搜索