先上個 Swift 中的 demo
:Method Swizzlinggit
其實 Swift 中實現原理和 OC 基本一致,只是蘋果爸爸再也不容許在 Swift 中使用+load()
和+initialize()
方法,這固然難不倒各類大神,那麼我就作次農夫山泉。。。github
先抽取 swizzling 的實現到NSObject
的擴展當中:swift
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
。ide
咱們用viewDidLoad
來作個dispatch_once
的示範:函數
extension UIViewController {
static func swizzleViewDidLoad() {
_ = self.swizzleMethod
}
@objc 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
被調用時候才調用。ui
OC 中+load()
方法會在類被裝載時調用,確保須要用到的方法都是被 Swizzling 過的。Swift 中能夠在AppDelegate
的init
方法中手動調用 swizzle 方法模擬+load()
實現。spa
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
super.init()
UIViewController.swizzleViewDidLoad()
}
}
複製代碼
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
。乍一看貌似沒有問題(其實最開始我絞盡腦汁也沒想清楚到底哪裏不對。。。),可是爲何各路大神都是用的第一種方式呢?網上有種說法:code
要先嚐試添加原 selector 是爲了作一層保護,由於若是這個類沒有實現原始方法"originalSel" ,但其父類實現了,那 class_getInstanceMethod 會返回父類的方法。這樣 method_exchangeImplementations 替換的是父類的那個方法,這固然不是你想要的。因此咱們先嚐試添加 originalSel ,若是已經存在,再用 method_exchangeImplementations 把原方法的實現跟新的方法實現給交換掉。blog
其實這種說法已經算是比較明確問題所在了,可是愚笨的我仍是沒有想通到底爲什就「這固然不是你想要的」了呢。
又是一番絞盡腦汁。。。終於 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