先聊聊Method Swizzling
從 Objective-C 開始, runtime一直是解決坑爹需求和面試裝逼的一大利器,然而聊起 runtime 很多人都第一個(gè)想到 Method Swizzling,.因?yàn)?Objective-C中調(diào)用方法都是動(dòng)態(tài)實(shí)現(xiàn)的,當(dāng)運(yùn)行時(shí)的才確定到底執(zhí)行哪個(gè)方法,而 Method Swizzling 就是利用這個(gè)特點(diǎn)來解決很多問題.
現(xiàn)在關(guān)于 Runtime 和 Method Swizzling 的文章太多了,我推薦一篇:
神經(jīng)病院Objective-C Runtime出院第三天——如何正確使用Runtime
@一縷殤流化隱半邊冰霜 大神寫的關(guān)于 runtime這幾篇,看完基本對 runtime 就沒什么問題了吧...
再看看 Swift3.0中的 Method Swizzling
先來看看 swizzling 在 Objective-C 中的注意點(diǎn):(對比上文鏈接中)
1.Swizzling應(yīng)該總在category的 +load中執(zhí)行 ( Objective-C )
那在 Swift 中, extension 并不是運(yùn)行時(shí)加載的, 因此也沒有加載時(shí)候就會(huì)被調(diào)用的類似 +load 的方法. 事實(shí)上,Swift 實(shí)現(xiàn)的 load 并不是在 app 運(yùn)行開始就被調(diào)用的。基于這些理由,我們使用另一個(gè)類初始化時(shí)會(huì)被調(diào)用的方法來進(jìn)行交換:
open override static func initialize() {
// Method Swizzling
}
這一條部分來自喵神 swiift tips 第二版, 喵神在第三版中刪除了 swizzling 這個(gè)章節(jié),理由是這部分更多是 Objective-C的內(nèi)容. 我個(gè)人覺得如果在 Swift 中還需要用 Swizzling 這種技術(shù)來實(shí)現(xiàn)需求, 不如用更 Swifty的方式去解決問題, 函數(shù)式或者面向協(xié)議等等等??
2.Swizzling應(yīng)該總是在dispatch_once中執(zhí)行
那么,問題來了,在3.0版本 dispatch once 已經(jīng)被廢棄,這怎么辦?
剛巧的是前幾天群里的老司機(jī) @沒故事的卓同學(xué) 寫了篇 [譯]Swift 3 中實(shí)現(xiàn)Dispatch once擴(kuò)展
通過給DispatchQueue實(shí)現(xiàn)一個(gè)擴(kuò)展方法來實(shí)現(xiàn) Dispatch once.
至于沒什么要 dispatch_once呢? 因?yàn)?Swizzling會(huì)改變?nèi)譅顟B(tài),所以用dispatch_once來確保無論多少線程都只會(huì)被執(zhí)行一次.
3. Swift自定義類中使用 Method Swizzling
因?yàn)镸ethod Swizzling的實(shí)現(xiàn)是基于 Objective-C 的動(dòng)態(tài)派發(fā)機(jī)制,所以有兩條限制
1.包含 swizzle 方法的類需要繼承自 NSObject
2.如果要 Swizzle 的是 Swift 類型的方法的話,需要將原方法和替換方法都加上 dynamic 標(biāo)記,以指明它們需要使用動(dòng)態(tài)派發(fā)機(jī)制
上個(gè) 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)
//在進(jìn)行 Swizzling 的時(shí)候,需要用 class_addMethod 先進(jìn)行判斷一下原有類中是否有要替換方法的實(shí)現(xiàn)
let didAddMethod: Bool = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
//如果 class_addMethod 返回 yes,說明當(dāng)前類中沒有要替換方法的實(shí)現(xiàn),所以需要在父類中查找,這時(shí)候就用到 method_getImplemetation 去獲取 class_getInstanceMethod 里面的方法實(shí)現(xiàn),然后再進(jìn)行 class_replaceMethod 來實(shí)現(xiàn) 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()
}
}