1. 程式人生 > >iOS --- 如何在Swift專案中使用runtime?

iOS --- 如何在Swift專案中使用runtime?

在Objective-C的專案中, 經常遇到通過runtime來獲取類和物件的成員變數, 屬性, 方法, 在此基礎上可以實現method swizzling.
關於runtime的相關內容, 請參考部落格:
iOS — 理解Runtime機制及其使用場景
iOS—防止UIButton重複點選的三種實現方式
iOS — 使用runtime解決3D Touch導致UIImagePicker崩潰的問題
JSPatch即使用JavaScriptCore.framework, 使用JS程式碼呼叫任何OC的原生介面, 通過runtime來替換任意OC的原生方法, 以此來實現實時地修復線上bug.

Swift中如何使用runtime

Swift程式碼中已經沒有了Objective-C的執行時訊息機制, 在程式碼編譯時即確定了其實際呼叫的方法. 所以純粹的Swift類和物件沒有辦法使用runtime, 更不存在method swizzling.
為了相容Objective-C, 凡是繼承NSObject的類都會保留其動態性, 依然遵循Objective-C的執行時訊息機制, 因此可以通過runtime獲取其屬性和方法, 實現method swizzling.
請看如下的程式碼:

//
//  UIButton+CSExtension.swift
//  CSSwiftExtension
// // Created by Chris Hu on 16/6/20. // Copyright © 2016年 icetime17. All rights reserved. // import UIKit // MARK: - UIButton Related public extension UIButton { private struct cs_associatedKeys { static var accpetEventInterval = "cs_acceptEventInterval" static var acceptEventTime = "cs_acceptEventTime"
} // 重複點選的間隔 var cs_accpetEventInterval: NSTimeInterval { get { if let accpetEventInterval = objc_getAssociatedObject(self, &cs_associatedKeys.accpetEventInterval) as? NSTimeInterval { return accpetEventInterval } return 1.0 } set { objc_setAssociatedObject(self, &cs_associatedKeys.accpetEventInterval, newValue as NSTimeInterval, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } var cs_acceptEventTime: NSTimeInterval { get { if let acceptEventTime = objc_getAssociatedObject(self, &cs_associatedKeys.acceptEventTime) as? NSTimeInterval { return acceptEventTime } return 1.0 } set { objc_setAssociatedObject(self, &cs_associatedKeys.acceptEventTime, newValue as NSTimeInterval, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } override public class func initialize() { let before: Method = class_getInstanceMethod(self, #selector(UIButton.sendAction(_:to:forEvent:))) let after: Method = class_getInstanceMethod(self, #selector(UIButton.cs_sendAction(_:to:forEvent:))) method_exchangeImplementations(before, after) } func cs_sendAction(action: Selector, to target: AnyObject?, forEvent event: UIEvent?) { if NSDate().timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_accpetEventInterval { return } if self.cs_accpetEventInterval > 0 { self.cs_acceptEventTime = NSDate().timeIntervalSince1970 } self.cs_sendAction(action, to: target, forEvent: event) } }

以上, 即通過runtime的方式解決UIButton的重複點選問題.
UIButton繼承自NSObject, 因此遵循runtime. 事實上, 對於基本框架如Foundation, UIKit等, 都可以使用runtime.
這裡, 要注意Swift的程式碼與Objective-C程式碼的語法區別.
同時, 對於一般OC程式碼的method swizzling, 在load方法中執行即可. 而Swift沒有load, 所以要在initialize中執行.
使用方式:

btn.cs_accpetEventInterval = 1.0

Swift中的@objc和dynamic關鍵字

繼承自NSObject的類都遵循runtime, 那麼純粹的Swift類呢?
在屬性和方法之前加上@objc關鍵字, 則一般情況下可以在runtime中使用了. 但有一些情況下, Swift會做靜態優化而無法使用runtime.
要想完全使得屬性和方法被動態呼叫, 必須使用dynamic關鍵字. 而dynamic關鍵字會隱式地加上@objc來修飾.
獲取Swift類的runtime資訊的方法, 要加上Swift模組名:

id cls = objc_getClass("DemoSwift.MySwiftClass")

關於Demo

本文的Demo請參考CSSwiftExtension.
這是是一個Swift的extension集合, 包含了一些常見的方法.