做過(guò)iOS開(kāi)發(fā)的同學(xué)都知道,runtime是OC這門(mén)動(dòng)態(tài)語(yǔ)言的一大特性。我們?yōu)榉诸?lèi)添加屬性蘑斧,或者h(yuǎn)ook某個(gè)方法都會(huì)用到runtime。因?yàn)檫\(yùn)行時(shí)特性须眷,OC也被認(rèn)為是一門(mén)動(dòng)態(tài)語(yǔ)言
竖瘾。
而Swift作為一門(mén)靜態(tài)語(yǔ)言
,它的類(lèi)型判斷和函數(shù)調(diào)用在編譯時(shí)已經(jīng)決定柒爸。那么准浴,本文將介紹為何要在Swift中使用runtime事扭,以及如何使用捎稚。
獲取類(lèi)的所有屬性和方法
class RuntimeHelper {
//獲取所有屬性
class func getAllIvars<T>(from type: T.Type) {
var count: UInt32 = 0
let ivars = class_copyIvarList(type as? AnyClass, &count)
for index in 0..<count {
guard let ivar = ivars?[Int(index)] else { continue }
guard let namePointer = ivar_getName(ivar) else { continue }
guard let name = String.init(utf8String: namePointer) else { continue }
print("ivar_name: \(name)")
}
}
//獲取所有方法
class func getAllMethods<T>(from type: T.Type) {
var count: UInt32 = 0
let methods = class_copyMethodList(type as? AnyClass, &count)
for index in 0..<count {
guard let method = methods?[Int(index)] else { continue }
let selector = method_getName(method)
let name = NSStringFromSelector(selector)
print("method_name: \(name)")
}
}
}
這里封裝了兩個(gè)類(lèi)方法,用于獲取一個(gè)類(lèi)的所有屬于和方法求橄。
//獲取UIView的所有屬性
RuntimeHelper.getAllIvars(from: UIView.self)
為類(lèi)添加關(guān)聯(lián)屬性
為一個(gè)類(lèi)增加方法今野,我們很容易想到在extension中添加。那么添加屬性罐农,我們自然也會(huì)想要在extension中實(shí)現(xiàn)条霜。
但extension中是不能包含存儲(chǔ)屬性的,因此我們必須使用計(jì)算屬性涵亏,那么為了避免在get方法里每次重新生成宰睡,我們需要使用到runtime。
例如為UIViewController增加一個(gè)名為loading的加載視圖
extension UIViewController {
private struct AssociateKeys {
static var loadingKey = "UIViewController+Extension+Loading"
}
//動(dòng)畫(huà)視圖
var loading: LOTAnimationView? {
get {
return objc_getAssociatedObject(self, &AssociateKeys.loadingKey) as? LOTAnimationView
}
set {
objc_setAssociatedObject(self, &AssociateKeys.loadingKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
//顯示動(dòng)畫(huà)
func showUtimesLoading() {
if self.loading == nil {
loading = LOTAnimationView(name: "loading_bubble")
loading!.frame.size = CGSize(width: 100, height: 100)
loading!.center = self.view.center
}
self.view.addSubview(self.loading!)
self.loading!.play()
}
//移除動(dòng)畫(huà)
func hideUtimeLoading(){
if let loading = self.loading {
loading.removeFromSuperview()
}
}
}
我們定義一個(gè)指針指向loading,并通過(guò)objc_getAssociatedObject和objc_setAssociatedObject實(shí)現(xiàn)該關(guān)聯(lián)屬性的get和set方法气筋。當(dāng)在某一個(gè)控制器需要顯示動(dòng)畫(huà)時(shí)拆内,直接調(diào)用showUtimesLoading,當(dāng)要移除動(dòng)畫(huà)時(shí)宠默,調(diào)用hideUtimeLoading
再看一個(gè)例子麸恍,試想我們要給一個(gè)UIView添加點(diǎn)擊事件,要做哪些步驟呢搀矫?
override func viewDidLoad() {
super.viewDidLoad()
let myView = UIView()
let ges = UITapGestureRecognizer(target: self, action: #selector(tapMyView))
myView.addGestureRecognizer(ges)
}
@objc func tapMyView() {
print("tap my view")
}
代碼很簡(jiǎn)單抹沪,可如果每次都要定義手勢(shì),定義方法瓤球,添加手勢(shì)融欧,實(shí)在是有些麻煩。并且有時(shí)候在代碼層面卦羡,我們只想關(guān)注點(diǎn)擊myView噪馏,發(fā)生了什么权她,不想關(guān)注方法和手勢(shì)的綁定。如果能用閉包的方式實(shí)現(xiàn)逝薪,例如下面這樣最好不過(guò)
myView.setTapActionWithClosure {
print("tap my view")
}
那么隅要,這就需要runtime的使用了。
先直接上代碼:(建議直接將下面一段扔到項(xiàng)目里實(shí)驗(yàn))
extension UIView {
// 定義手勢(shì)和閉包關(guān)聯(lián)的Key
private struct AssociateKeys {
static var gestureKey = "UIView+Extension+gestureKey"
static var closureKey = "UIView+Extension+closureKey"
}
// 為view添加點(diǎn)擊事件
func setTapActionWithClosure(_ closure: @escaping ()->()) {
var gesture = objc_getAssociatedObject(self, &AssociateKeys.gestureKey)
if gesture == nil {
gesture = UITapGestureRecognizer(target: self, action: #selector(handleActionForTapGesture(_:)))
addGestureRecognizer(gesture as! UIGestureRecognizer)
isUserInteractionEnabled = true
objc_setAssociatedObject(self, &AssociateKeys.gestureKey, gesture, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
objc_setAssociatedObject(self, &AssociateKeys.closureKey, closure, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY)
}
// 點(diǎn)擊手勢(shì)實(shí)際調(diào)用的函數(shù)
@objc private func handleActionForTapGesture(_ gesture: UITapGestureRecognizer) {
if gesture.state == UIGestureRecognizer.State.recognized {
let obj = objc_getAssociatedObject(self, &AssociateKeys.closureKey)
if let action = obj as? ()->() {
action()
}
}
}
}
在setTapActionWithClosure的實(shí)現(xiàn)中可以看到:
1.我們通過(guò)objc_getAssociatedObject獲取與view相關(guān)聯(lián)的手勢(shì)
2.判斷若手勢(shì)為空董济,則新建手勢(shì)步清,并綁定給view,手勢(shì)的點(diǎn)擊事件為handleActionForTapGesture虏肾。再通過(guò)objc_setAssociatedObject將該手勢(shì)和該view關(guān)聯(lián)起來(lái)
3.將事件閉包和該view關(guān)聯(lián)起來(lái)
而從handleActionForTapGesture的實(shí)現(xiàn)可以看到廓啊,點(diǎn)擊事件實(shí)際上是調(diào)用在3中和view關(guān)聯(lián)的閉包
和OC中使用有什么不同?
- 不需要導(dǎo)入#import <objc/runtime.h>封豪,可直接調(diào)用objc_getAssociatedObject等方法
- 為類(lèi)動(dòng)態(tài)添加方法谴轮,OC在分類(lèi)中實(shí)現(xiàn),Swift在extension中實(shí)現(xiàn)(添加屬性也是如此)
- OC對(duì)Key的定義多用常量或_cmd,而Swift常用私有結(jié)構(gòu)體的靜態(tài)變量
還有那些使用方式吹埠?
runtime的另一大功能:method_swizzling第步。
如果想對(duì)系統(tǒng)的方法進(jìn)行hook,例如替換掉系統(tǒng)viewwillappear缘琅,按照OC的做法粘都,我們會(huì)在+load或+initialize進(jìn)行方法的交換。因?yàn)镾wift里無(wú)法調(diào)用+load或+initialize,因此建議method_swizzling的功能用OC完成刷袍。程序會(huì)在啟動(dòng)時(shí)翩隧,執(zhí)行這些方法的交換。
結(jié)論:
Swift作為一門(mén)靜態(tài)語(yǔ)言呻纹,擁有比OC更加安全的機(jī)制堆生。但在很多時(shí)候,為了開(kāi)發(fā)方便雷酪,更為了某些特殊功能(例如對(duì)于系統(tǒng)方法的替換)淑仆,我們需要用OC的特性,Runtime來(lái)實(shí)現(xiàn)太闺。