交換方法Method Swizzling[swift]
1. dispatch_once替代方案
OC中用來保證程式碼塊只執行一次的dispatch_once
在swfit中已經被廢棄了,取而代之的是使用static let
,let
本身就帶有執行緒安全性質的.
例如單例的實現.
final public class MySingleton { static let shared = MySingleton() private init() {} }
但如果我們不想定義常量,需要某個程式碼塊執行一次呢?
private lazy var takeOnceTime: Void = { // 程式碼塊... }() _ = takeOnceTime
定義一個懶載入的變數,防止在初始化的時候被執行.後面加一個void
,為了在_ = takeOnceTime
賦值時不耗效能,返回一個Void
型別.
lazy var
改為static let
也可以,為了使用方便,我們用一個類方法封裝下
class ClassName { private static let takeOnceTime: Void = { // 程式碼塊... }() static func takeOnceTimeFunc() { ClassName.takeOnceTime } } // 使用 ClassName.takeOnceTimeFunc()
這樣就可以做到和dispatch_once
一樣的效果了.
2. 被廢棄的+load()和+initialize()
我們都知道OC中兩個方法+load()
和+initialize()
.
+load()
:app啟動的時候會載入所有的類,此時就會呼叫每個類的load方法.
+initialize()
: 第一次初始化這個類的時候會被呼叫.
然而在目前的swift版本中這兩個方法都不可用了,那現在我們要在這個階段搞事情該怎麼做? 例如method swizzling
.
ofollow,noindex">JORDAN SMITH大神
給出了一種很巧解決方案.UIApplication
有一個next
屬性,它會在applicationDidFinishLaunching
之前被呼叫,這個時候通過runtime
獲取到所有類的列表,然後向所有遵循SelfAware協議的類傳送訊息.
extension UIApplication { private static let runOnce: Void = { NothingToSeeHere.harmlessFunction() }() override open var next: UIResponder? { // Called before applicationDidFinishLaunching UIApplication.runOnce return super.next } } protocol SelfAware: class { static func awake() } class NothingToSeeHere { static func harmlessFunction() { let typeCount = Int(objc_getClassList(nil, 0)) let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount) let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types) objc_getClassList(autoreleasingTypes, Int32(typeCount)) for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() } types.deallocate() } }
之後任何遵守SelfAware
協議實現的+awake()
方法在這個階段都會被呼叫.
3. 交換方法 Method Swizzling
黑魔法Method Swizzling
在swift中實現的兩個困難點
- swizzling 應該保證只會執行一次.
- swizzling 應該在載入所有類的時候呼叫.
分別在上面給出瞭解決方案.
下面給出了兩個示例供參考:
protocol SelfAware: class { static func awake() static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) } extension SelfAware { static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) { let originalMethod = class_getInstanceMethod(forClass, originalSelector) let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) guard (originalMethod != nil && swizzledMethod != nil) else { return } if class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!)) { class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!)) } else { method_exchangeImplementations(originalMethod!, swizzledMethod!) } } } class NothingToSeeHere { static func harmlessFunction() { let typeCount = Int(objc_getClassList(nil, 0)) let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount) let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types) objc_getClassList(autoreleasingTypes, Int32(typeCount)) for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() } types.deallocate() } } extension UIApplication { private static let runOnce: Void = { NothingToSeeHere.harmlessFunction() }() override open var next: UIResponder? { UIApplication.runOnce return super.next } }
在SelfAware
的extension
中為swizzlingForClass
做了預設實現,相當於一層封裝.
1. 給按鈕新增點選計數
extension UIButton: SelfAware { static func awake() { UIButton.takeOnceTime } private static let takeOnceTime: Void = { let originalSelector = #selector(sendAction) let swizzledSelector = #selector(xxx_sendAction(action:to:forEvent:)) swizzlingForClass(UIButton.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector) }() @objc public func xxx_sendAction(action: Selector, to: AnyObject!, forEvent: UIEvent!) { struct xxx_buttonTapCounter { static var count: Int = 0 } xxx_buttonTapCounter.count += 1 print(xxx_buttonTapCounter.count) xxx_sendAction(action: action, to: to, forEvent: forEvent) } }
2. 替換控制器的viewWillAppear
方法
extension UIViewController: SelfAware { static func awake() { swizzleMethod } private static let swizzleMethod: Void = { let originalSelector = #selector(viewWillAppear(_:)) let swizzledSelector = #selector(swizzled_viewWillAppear(_:)) swizzlingForClass(UIViewController.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector) }() @objc func swizzled_viewWillAppear(_ animated: Bool) { swizzled_viewWillAppear(animated) print("swizzled_viewWillAppear") } }
本文收錄於SwiftTips" target="_blank" rel="nofollow,noindex">SwiftTips
如有疑問,歡迎留言 :-D