Method Swizzling 为什么要先调用 class_addMethod?
先上個 Swift 中的 demo:Method Swizzling
Swift 中的實現
其實 Swift 中實現原理和 OC 基本一致,只是蘋果爸爸不再允許在 Swift 中使用+load()和+initialize()方法,這當然難不倒各種大神,那么我就做次農夫山泉。。。
Swizzling
先抽取 swizzling 的實現到NSObject的擴展當中:
extension NSObject {static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {guard let originalMethod = class_getInstanceMethod(forClass, originalSelector),let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else {return}let isAddSuccess = class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))if isAddSuccess {class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))} else {method_exchangeImplementations(originalMethod, swizzledMethod)}} } 復制代碼可以看到核心實現和 OC 是完全一致的,那么剩下的就是模擬 OC 版本實現中的+load()和dispatch_once。
dispatch_once
我們用viewDidLoad來做個dispatch_once的示范:
extension UIViewController {static func swizzleViewDidLoad() {_ = self.swizzleMethod} func swizzled_viewDidLoad() {swizzled_viewDidLoad()print("嘻嘻")}private static let swizzleMethod: Void = {let originalSelector = #selector(viewDidLoad)let swizzledSelector = #selector(swizzled_viewDidLoad)swizzlingForClass(UIViewController.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)}() } 復制代碼在 Swift 中static let這樣聲明的變量其實已經用到dispatch_once,而且static自帶lazy屬性,要在封裝函數swizzleViewDidLoad被調用時候才調用。
+load()
OC 中+load()方法會在類被裝載時調用,確保需要用到的方法都是被 Swizzling 過的。Swift 中可以在AppDelegate 的init方法中手動調用 swizzle 方法模擬+load()實現。
class AppDelegate: UIResponder, UIApplicationDelegate {override init() {super.init()UIViewController.swizzleViewDidLoad()} } 復制代碼為什么要先調用 class_addMethod?
class_addMethod這個方法是很容易被人忽視的,對于 Swizzling 一節中的代碼,還有一種常見的寫法:
extension NSObject {static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {guard let originalMethod = class_getInstanceMethod(forClass, originalSelector),let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else {return}method_exchangeImplementations(originalMethod, swizzledMethod)} } 復制代碼這種方式就只是簡單的直接交換了originalMethod和swizzledMethod。乍一看貌似沒有問題(其實最開始我絞盡腦汁也沒想清楚到底哪里不對。。。),但是為什么各路大神都是用的第一種方式呢?網上有種說法:
要先嘗試添加原 selector 是為了做一層保護,因為如果這個類沒有實現原始方法"originalSel" ,但其父類實現了,那 class_getInstanceMethod 會返回父類的方法。這樣 method_exchangeImplementations 替換的是父類的那個方法,這當然不是你想要的。所以我們先嘗試添加 originalSel ,如果已經存在,再用 method_exchangeImplementations 把原方法的實現跟新的方法實現給交換掉。
其實這種說法已經算是比較明確問題所在了,但是愚笨的我還是沒有想通到底為什就“這當然不是你想要的”了呢。
又是一番絞盡腦汁。。。終于 Biuer 的一下想通了
在舉栗子前引用一段對 Selectors、Methods 和 Implementations 理解:
理解 selector, method, implementation 這三個概念之間關系的最好方式是:在運行時,類(Class)維護了一個消息分發列表來解決消息的正確發送。每一個消息列表的入口是一個方法(Method),這個方法映射了一對鍵值對,其中鍵值是這個方法的名字 selector(SEL),值是指向這個方法實現的函數指針 implementation(IMP)。 Method swizzling 修改了類的消息分發列表使得已經存在的 selector 映射了另一個實現 implementation,同時重命名了原生方法的實現為一個新的 selector。
假設父類有個方法method,子類未重寫method方法,子類的中想要拿來替換的方法為swizzledMethod。
用第二種方式進行方法交換
- 在子類的實例中調用method方法時,確實按預期正常運行的
- 在父類的實例中調用method方法時,就開始崩潰了。因為方法交換后,method方法的IMP其實和子類swizzledMethod的IMP進行了交換,此時等同于父類調用子類方法,當然會崩潰。
用第一種方式進行方法交換
class_addMethod先判斷了子類中是否有method方法
- 如果有,則添加失敗,直接進行交換
- 如果沒有,則添加成功,將swizzledMethod的IMP賦值給method這個Selector,然后在將method的IMP(其實是父類中的實現)賦值給swizzledMethod這個Selector
轉載于:https://juejin.im/post/5cb6df44e51d456e6f45c6f1
總結
以上是生活随笔為你收集整理的Method Swizzling 为什么要先调用 class_addMethod?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hybrid app、web app与n
- 下一篇: padavan 源码