Swift3中的 Method Swizzling

一銘發表於2017-12-13

先聊聊Method Swizzling

從 Objective-C 開始, runtime一直是解決坑爹需求和麵試裝逼的一大利器,然而聊起 runtime 很多人都第一個想到 Method Swizzling,.因為 Objective-C中呼叫方法都是動態實現的,當執行時的才確定到底執行哪個方法,而 Method Swizzling 就是利用這個特點來解決很多問題.

現在關於 Runtime 和 Method Swizzling 的文章太多了,我推薦一篇: 神經病院Objective-C Runtime出院第三天——如何正確使用Runtime @一縷殤流化隱半邊冰霜 大神寫的關於 runtime這幾篇,看完基本對 runtime 就沒什麼問題了吧...

再看看 Swift3.0中的 Method Swizzling

先來看看 swizzling 在 Objective-C 中的注意點:(對比上文連結中)

1.Swizzling應該總在category的 +load中執行 ( Objective-C )

那在 Swift 中, extension 並不是執行時載入的, 因此也沒有載入時候就會被呼叫的類似 +load 的方法. 事實上,Swift 實現的 load 並不是在 app 執行開始就被呼叫的。基於這些理由,我們使用另一個類初始化時會被呼叫的方法來進行交換:

open override static func initialize() {
    // Method Swizzling
}
複製程式碼

這一條部分來自喵神 swiift tips 第二版, 喵神在第三版中刪除了 swizzling 這個章節,理由是這部分更多是 Objective-C的內容. 我個人覺得如果在 Swift 中還需要用 Swizzling 這種技術來實現需求, 不如用更 Swifty的方式去解決問題, 函式式或者面向協議等等等?

2.Swizzling應該總是在dispatch_once中執行

那麼,問題來了,在3.0版本 dispatch once 已經被廢棄,這怎麼辦?

剛巧的是前幾天群裡的老司機 @沒故事的卓同學 寫了篇 [譯]Swift 3 中實現Dispatch once擴充套件

通過給DispatchQueue實現一個擴充套件方法來實現 Dispatch once. 至於沒什麼要 dispatch_once呢? 因為 Swizzling會改變全域性狀態,所以用dispatch_once來確保無論多少執行緒都只會被執行一次.

3. Swift自定義類中使用 Method Swizzling

因為Method Swizzling的實現是基於 Objective-C 的動態派發機制,所以有兩條限制 1.包含 swizzle 方法的類需要繼承自 NSObject 2.如果要 Swizzle 的是 Swift 型別的方法的話,需要將原方法和替換方法都加上 dynamic 標記,以指明它們需要使用動態派發機制


上個 sample:

extension UIViewController {
    open override static func initialize() {
        struct Static {
            static var token = NSUUID().uuidString
        }

        if self != UIViewController.self {
            return
        }

        DispatchQueue.once(token: Static.token) { 
            let originalSelector = #selector(UIViewController.viewWillAppear(_:))
            let swizzledSelector = #selector(UIViewController.xl_viewWillAppear(animated:))

            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

            
            //在進行 Swizzling 的時候,需要用 class_addMethod 先進行判斷一下原有類中是否有要替換方法的實現
            let didAddMethod: Bool = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
            //如果 class_addMethod 返回 yes,說明當前類中沒有要替換方法的實現,所以需要在父類中查詢,這時候就用到 method_getImplemetation 去獲取 class_getInstanceMethod 裡面的方法實現,然後再進行 class_replaceMethod 來實現 Swizzing

            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
        }
    }

    func xl_viewWillAppear(animated: Bool) {
        self.xl_viewWillAppear(animated: animated)
        print("xl_viewWillAppear in swizzleMethod")
    }
}

extension DispatchQueue {
    private static var onceTracker = [String]()

    open class func once(token: String, block:() -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        if onceTracker.contains(token) {
            return
        }
        
        onceTracker.append(token)
        block()
    }
}
複製程式碼

相關文章