iOS開發(fā)之 Method Swizzling 深入淺出
只要善用Google鹃栽,網(wǎng)上有很多關(guān)于
Method Swizzling
的Demo宵蛀,在這里我就不打算貼代碼了蚯妇,主要介紹下概念松靡,原理键俱,注意事項等等。
開發(fā)需求
如果產(chǎn)品經(jīng)理突然說:"在所有頁面添加統(tǒng)計功能提佣,也就是用戶進(jìn)入這個頁面就統(tǒng)計一次"吮蛹。我們會想到下面的一些方法:
- 手動添加
直接簡單粗暴的在每個控制器中加入統(tǒng)計,復(fù)制拌屏、粘貼潮针、復(fù)制、粘貼...
上面這種方法太Low了倚喂,消耗時間而且以后非常難以維護(hù)每篷,會讓后面的開發(fā)人員罵死的。
- 繼承
我們可以使用繼承的方式來解決這個問題端圈。創(chuàng)建一個基類焦读,在這個基類中添加統(tǒng)計方法,其他類都繼承自這個基類舱权。
然而矗晃,這種方式修改還是很大,而且定制性很差宴倍。以后有新人加入之后张症,都要囑咐其繼承自這個基類仓技,所以這種方式并不可取。
Category
我們可以為UIViewController
建一個Category
俗他,然后在所有控制器中引入這個Category
脖捻。當(dāng)然我們也可以添加一個PCH
文件,然后將這個Category
添加到PCH
文件中兆衅。
Method Swizzling
我們可以使用蘋果的“黑魔法”Method Swizzling
地沮,Method Swizzling
本質(zhì)上就是對IMP
和SEL
進(jìn)行交換。
先了解幾個概念
Selectors, Methods, & Implementations
在 Objective-C
的運(yùn)行時中羡亩,selectors
, methods
, implementations
指代了不同概念摩疑,然而我們通常會說在消息發(fā)送過程中,這三個概念是可以相互轉(zhuǎn)換的畏铆。 下面是蘋果 Objective-C Runtime Reference
中的描述:
Selector(typedef struct objc_selector *SEL)
:在運(yùn)行時Selectors
用來代表一個方法的名字未荒。Selector
是一個在運(yùn)行時被注冊(或映射)的C類型字符串。Selector
由編譯器產(chǎn)生并且在當(dāng)類被加載進(jìn)內(nèi)存時由運(yùn)行時自動進(jìn)行名字和實現(xiàn)的映射及志。Method(typedef struct objc_method *Method)
:方法是一個不透明的用來代表一個方法的定義的類型片排。Implementation(typedef id (*IMP)(id, SEL,...))
:這個數(shù)據(jù)類型指向一個方法的實現(xiàn)的最開始的地方。該方法為當(dāng)前CPU架構(gòu)使用標(biāo)準(zhǔn)的C方法調(diào)用來實現(xiàn)速侈。該方法的第一個參數(shù)指向調(diào)用方法的自身(即內(nèi)存中類的實例對象率寡,若是調(diào)用類方法,該指針則是指向元類對象(metaclass
)倚搬。第二個參數(shù)是這個方法的名字selector
冶共,該方法的真正參數(shù)緊隨其后。
理解 selector
, method
, implementation
這三個概念之間關(guān)系的最好方式是:在運(yùn)行時每界,類(Class
)維護(hù)了一個消息分發(fā)列表來解決消息的正確發(fā)送捅僵。每一個消息列表的入口是一個方法(Method
),這個方法映射了一對鍵值對眨层,其中鍵值是這個方法的名字 selector(SEL)
庙楚,值是指向這個方法實現(xiàn)的函數(shù)指針 implementation(IMP)
。 Method swizzling
修改了類的消息分發(fā)列表使得已經(jīng)存在的 selector
映射了另一個實現(xiàn) implementation
趴樱,同時重命名了原生方法的實現(xiàn)為一個新的 selector
馒闷。
Method Swizzling原理
Method Swizzing
是發(fā)生在運(yùn)行時的,主要用于在運(yùn)行時將兩個Method
進(jìn)行交換叁征,我們可以將Method Swizzling
代碼寫到任何地方纳账,但是只有在這段Method Swilzzling
代碼執(zhí)行完畢之后互換才起作用。
Method Swizzling 使用注意
類簇設(shè)計模式
在iOS中NSNumber捺疼、NSArray疏虫、NSDictionary等這些類都是類簇(Class Clusters
),一個NSArray的實現(xiàn)可能由多個類組成。
所以如果想對NSArray進(jìn)行Swizzling卧秘,必須獲取到其“真身”進(jìn)行Swizzling尤蛮,直接對NSArray進(jìn)行操作是無效的。
下面列舉了NSArray和NSDictionary本類的類名斯议,可以通過Runtime函數(shù)取出本類。
類名 | 真身 |
---|---|
NSArray | __NSArrayI |
NSMutableArray | __NSArrayM |
NSDictionary | __NSDictionaryI |
NSMutableDictionary | __NSDictionaryM |
注意要點
- Swizzling應(yīng)該總在
+load
中執(zhí)行 - Swizzling應(yīng)該總是在
dispatch_once
中執(zhí)行 - Swizzling在
+load
中執(zhí)行時醇锚,不要調(diào)用[super load]
哼御。如果多次調(diào)用了[super load]
,可能會出現(xiàn)“Swizzle無效”的假象焊唬,原理見下圖:
Swift 自定義類中使用 Method Swizzling
要在 Swift 自定義類中使用 Method Swizzling 有兩個必要條件:
- 包含 Swizzle 方法的類需要繼承自 NSObject
- 需要 Swizzle 的方法必須有動態(tài)屬性(dynamic attribute)
注:對于 Swift 的自定義類恋昼,因為默認(rèn)并沒有使用 Objective-C 運(yùn)行時,因此也沒有動態(tài)派發(fā)的方法列表赶促,所以如果要 Swizzle 的是 Swift 類型的方法的話液肌,是需要將原方法和替換方法都加上 dynamic 標(biāo)記,以指明它們需要使用動態(tài)派發(fā)機(jī)制鸥滨。當(dāng)然類也要繼承自 NSObject嗦哆。
再注:下面這個例子使用了 Objective-C 的動態(tài)派發(fā),對于 NSObject 的子類(UIViewController)是可以直接使用的婿滓,并不是 Swift 中自定義的類老速,因此沒有加 dynamic 標(biāo)記也是可以的。
Method Swizzling 中 Objective-C 與 Swift 的異同
區(qū)別 | Objective-C | Swift |
---|---|---|
Runtime 頭文件 | #import <objc/runtime.h> |
不需要 |
Swizzling 調(diào)用處 |
load 方法 |
initialize 方法 |
注:load
方法只在 Objective-C 里有凸主,而且不能在 Swift 里重載橘券,不管怎么試都會報編譯錯誤。接下來執(zhí)行 Swizzle 最好的地方就是 initialize
了卿吐,這是調(diào)用第一個方法前的地方旁舰。
因為 Swizzling 會改變?nèi)譅顟B(tài),所以我們需要在運(yùn)行時采取一些預(yù)防措施嗡官。GCD 的dispatch_once
可以保證操作的原子性箭窜,確保代碼只被執(zhí)行一次,不管有多少個線程衍腥。
Method Swizzling 實際應(yīng)用
APM(應(yīng)用性能管理)
網(wǎng)絡(luò)監(jiān)控的原理绽快,應(yīng)該就是hook NSURLConnection
, NSURLSession
。崩潰收集的原理紧阔,應(yīng)該就是hook NSException
坊罢。
-
https://newrelic.com/
國外行業(yè)老大
-
http://www.tingyun.com/
國內(nèi)聽云
-
http://www.oneapm.com/
國內(nèi)OneAPM
-
http://apm.netease.com/
國內(nèi)網(wǎng)易
國外資料 ??
可能需要梯子
- http://nshipster.com/method-swizzling/
- https://medium.com/rocknnull/ios-to-swizzle-or-not-to-swizzle-f8b0ed4a1ce6
- https://medium.com/@abhimuralidharan/method-swizzling-in-ios-swift-1f38edaf984f
- https://darkdust.net/writings/objective-c/method-swizzling
- https://blog.newrelic.com/2014/04/16/right-way-to-swizzle/
- https://spin.atomicobject.com/2014/12/30/method-swizzling-objective-c/
- https://wiredcraft.com/blog/method-swizzling-ios/
- https://www.raywenderlich.com/177890/swizzling-in-ios-11-with-uidebugginginformationoverlay
- http://petersteinberger.com/blog/2014/a-story-about-swizzling-the-right-way-and-touch-forwarding/
- https://iossolves.blogspot.com/2017/11/swift-4-method-swizzling-part-12.html
- http://iossolves.blogspot.com/2017/11/swift-4-method-swizzling-part-22.html
- https://sexyswift.wordpress.com/tag/method-swizzling/
- https://qiita.com/paming/items/25eaf89e4f448ab05752
- http://www.swift-studies.com/blog/2014/7/13/method-swizzling-in-swift
- https://academy.realm.io/posts/sash-zats-swift-swizzling/
小廣告 ??
GitHub開源了一款iOS調(diào)試小工具,功能之一就是實現(xiàn)網(wǎng)絡(luò)請求抓包(代碼零入侵),原理也是使用了
Method Swizzling
, 感興趣的童鞋可以進(jìn)來看看, 也歡迎使用?? http://DotzuX.com