寫在前面
關於 Method Swizzling 這個東西,已經有很多高人寫了詳細的文章來介紹,我就不再班門弄斧,往深了說了。
而且不作延伸的話,這項技術本身也沒有複雜到要長文論述的地步。
本文旨在幫助不熟悉這項技術的人,開始在實際開發過程中,嘗試使用它。
這是個啥
- swizz 這個詞在英語裡面是“欺騙”的意思。
Method Swizzling 也叫做“方法調配”、“方法混合”、“方法調和”,是用來互換兩個方法的實現的技巧。 - 這東西並不常用,比如我們用方法 A 實現了 a 這件事,方法 B 實現了 b 這件事,現在你非要用 A 實現 b,B 實現 a,即便技術上是可行的,你圖個啥?回頭再換回來你還記得不?再換第三次呢?
- 那麼什麼時候可能需要用到這個東西呢?除錯的時候。
如果方法 A、B 我都知道怎麼實現的,那確實不用換。但是假如方法 A 的實現被隱藏了,那麼我是不是可以用方法 B 呼叫方法 A,再順便新增點別的功能,然後進行 A、B 實現 swizz。
這樣再呼叫方法 A 的時候,就多了一點我們之前順便新增的功能。
有人會說,你這有意思麼,你直接呼叫方法 B 不就得了,為啥還要換?重點在於,方法 A 如何被呼叫可能不是我們可以決定的啊。或許這個方法已經在無數個地方被呼叫了無數次,那我想批量替換的話,當然就可以 swizz 了。
舉栗子
比如說,在某個專案中,NSArray 例項的下面這個方法被呼叫了 N 多次
1 |
func containsObject(anObject: AnyObject) -> Bool |
現在我想除錯一下,看看如果這個方法返回 true,即陣列包含我們傳入的元素的時候,這個元素在資料的什麼位置(index)。
1 |
func indexOfObject(anObject: AnyObject) -> Int |
當然直接呼叫上面這個方法就可以知道 index,但是 containsObject
被使用了太多次,Xcode 現在又不支援 Swift 重構,懶得改了。那就寫個新方法,給原方法加個可以輸出 index 的功能,再用 swizz 替換一下兩個方法的實現吧。
這裡我貼了完整的一個 demo 的程式碼,你可以直接粘到 Xcode 裡面執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let arr = NSArray(array: ["Swift","Method","Swizzling"]) print("-----Swizzling 之前-----") print(arr.containsObject("Swift")) // true NSArray.swizz() // 方法互換 print("-----Swizzling 之後-----") print(arr.containsObject("Swift")) // 先輸出 index,再 true NSArray.swizz() // 方法再換回來 print("-----Swizzling 兩下-----") print(arr.containsObject("Swift")) // true } } extension NSArray { // 用來和預設方法進行替換的方法 func myContainsObject(anObject: AnyObject) -> Bool { // 輸出元素的 index,這是預設的原方法不具有的功能 if self.myContainsObject(anObject) { print("index:\(self.indexOfObject(anObject))") } // 不會產生死迴圈,因為執行期間,下面的方法已經被替換成了預設的 containsObject return self.myContainsObject(anObject) } // 用來給不同方法互相替換的方法 class func swizz() { let originalMethod = class_getInstanceMethod(NSArray.self, #selector(containsObject(_:))) let swizzledMethod = class_getInstanceMethod(NSArray.self, #selector(myContainsObject(_:))) method_exchangeImplementations(originalMethod, swizzledMethod) } } |
【注意幾點】
- 這裡我先後呼叫了三次
containsObject
這個方法,其中第二次,它的內部實現被myContainsObject
這個方法的內部實現替換掉了。 myContainsObject
這個方法乍一看是死迴圈,如果你直接呼叫它的話,它也確實是死迴圈。但現在我們是在 RunTime 期間,動態地決定這個方法的內部實現的,在我們呼叫這個方法,進入它的函式體的時候,它的實現就已經被換掉了,所以在它的內部,你應該把myContainsObject
這個詞在你的腦子裡換成containsObject
(如果確定此時兩個方法確實互換了實現)。
最後
如果我寫的這點東西可以幫助你以後的 debug 工作,那麼最好。
如果你要在實際專案裡用它……你要是真能用上也挺厲害。