作者:uraimo,原文鏈接,原文日期:2015-10-23
譯者:小鐵匠Linus;校對:100mango;定稿:Channe
示例程序可以從 Github 上下載。
(譯者注:譯者也寫了一篇關于 Method Swizzling 的文章 Runtime 之 Method Swizzling,順便寫了兩個小 demo 來對比 Objective-C 和 Swift 之間 Method Swizzling 的異同,示例代碼傳送門)
Method Swizzling 在 Objective-C 或其他語言中是一種很有名的技術,用來支持動態(tài)方法派發(fā)。
Method Swizzling 通過改變特定 selector(方法)與實際實現(xiàn)之間的映射,在 runtime 時將一個方法的實現(xiàn)替換成其它方法的實現(xiàn)。
雖然這看起來非常方便,但是這個功能也有其缺點。在 runtime 執(zhí)行這類更改時,你就不能在編譯時利用那些可用的安全檢查了。因此,應該小心使用 Method Swizzling。
NSHipster 上有一篇關于如何在 Objective-C 中使用 Method Swizzling 的文章(譯者注:南峰子的技術博客有這篇文章的譯文)(其他詳情也可以看這里)以及 Stackoverflow 上有一些如何使用 Method Swizzling 的討論。
Swift 關于方法派發(fā)是使用靜態(tài)方法的,但有些情形可能需要用到 Method Swizzling。
在 Swift 中使用 Method Swizzling 之前,讓我再次重申一下這種技術還是盡量少用,只有當你的問題不能用 Swift 的方式解決,也不能用子類、協(xié)議或擴展解決時才使用。
正如 NSHipster 上另一篇文章描述的那樣,在 Swift 中對一個來自基本框架(Foundation、UIKit 等)的類使用 Method Swizzling 與 Objective-C 沒什么區(qū)別。
extension UIViewController { public override static func initialize() { struct Static { static var token: dispatch_once_t = 0 } // 確保不是子類 if self !== UIViewController.self { return } dispatch_once(&Static.token) { let originalSelector = Selector("viewWillAppear:") let swizzledSelector = Selector("newViewWillAppear:") let originalMethod = class_getInstanceMethod(self, originalSelector) let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) if didAddMethod { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } } // MARK: - Method Swizzling func newViewWillAppear(animated: Bool) { self.newViewWillAppear(animated) if let name = self.descriptiveName { print("viewWillAppear: \(name)") } else { print("viewWillAppear: \(self)") } } }
在這個例子中,應用中的每個 UIViewController 都會執(zhí)行額外的操作,而原始的 viewWillAppear 方法會被保護起來不執(zhí)行,這種情形只能通過 Method Swizzling 來實現(xiàn)。
viewWillAppear 方法的實現(xiàn)會被替換成 initialize 中 newViewWillAppear 方法的實現(xiàn)。值得注意的是,在 swizzle 后,會遞歸調用 newViewWillAppear 方法,以替換之前原始的 viewWillAppear 方法。
與 Objective-C 的第一個不同之處就是 swizzling 不在 load 方法里執(zhí)行。
加載一個類的定義時,會調用 load 方法,因此這地方適合執(zhí)行 Method Swizzling 。
但是 load 方法只在 Objective-C 里有,而且不能在 Swift 里重載,不管怎么試都會報編譯錯誤。接下來執(zhí)行 swizzle 最好的地方就是 initialize 了,這是調用你的類中第一個方法前的地方。
所有對修改方法執(zhí)行的操作放在 dispatch_once 中,這是為了確保這個過程只執(zhí)行一次。
這些都是你要知道的關于繼承自基本框架或 Objective-C 橋接類的。如果你要在純 Swift 類中正確使用 Method Swizzling 的話,就需要將下面這些注意事項記在心上。
要在 Swift 自定義類中使用 Method Swizzling 有兩個必要條件:
包含 swizzle 方法的類需要繼承自 NSObject
需要 swizzle 的方法必須有動態(tài)屬性(dynamic attribute)
更多關于為什么必須需要這些可以在蘋果的文檔里查看:
需要動態(tài)派發(fā)
當 @objc 屬性將你的 Swift API 暴露給 Objective-C runtime 時,不能確保屬性、方法、初始器的動態(tài)派發(fā)。Swift 編譯器可能為了優(yōu)化你的代碼,而繞過 Objective-C runtime。當你指定一個成員變量 為 dynamic 時,訪問該變量就總會動態(tài)派發(fā)了。因為在變量定義時指定 dynamic 后,會使用 Objective-C runtime 來派發(fā)。
動態(tài)派發(fā)是必須的。但是,你必須使用 dynamic 修飾才能知道在運行時 API 的實現(xiàn)被替換了。舉個例子,你可以在 Objective-C runtime 使用 method_exchangeImplementations 方法來交換兩個方法的實現(xiàn)。假如 Swift 編譯器將方法的實現(xiàn)內聯(lián)(inline)了或者訪問去虛擬化(devirtualize)了,這個交換過來的新的實現(xiàn)就無效了。
說白了,如果你想要替換的方法沒有聲明為 dynamic 的話,就不能 swizzle。
翻譯成代碼就是這樣:
class TestSwizzling : NSObject { dynamic func methodOne()->Int{ return 1 } } extension TestSwizzling { //在 Objective-C 中,我們在 load() 方法進行 swizzling。但Swift不允許使用這個方法。 override class func initialize() { struct Static { static var token: dispatch_once_t = 0; } // 只執(zhí)行一次 dispatch_once(&Static.token) { let originalSelector = Selector("methodOne"); let swizzledSelector = Selector("methodTwo"); let originalMethod = class_getInstanceMethod(self, originalSelector); let swizzledMethod = class_getInstanceMethod(self, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); } } func methodTwo()->Int{ // swizzling 后, 該方法就不會遞歸調用 return methodTwo()+1 } } var c = TestSwizzling() print(c.methodOne()) //2 print(c.methodTwo()) //1
這個簡單的例子中,在 TestSwizzling 對象第一個方法被調用前,methodOne 和 methodTwo 的實現(xiàn)被相互替換了。
正如你看到的,在 Swift 中還是可以使用 Method Swizzling 的,只是我的意見是,絕大部分時候不應該出現(xiàn)在實際的產品代碼里。如果你想通過 swizzling 快速解決問題的話,那更好的辦法就是想出一個更好的架構,重構你的代碼。
本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 http://swift.gg。