Swift開(kāi)發(fā)中關(guān)于runtime的使用

做過(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)太闺。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末糯景,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子省骂,更是在濱河造成了極大的恐慌蟀淮,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钞澳,死亡現(xiàn)場(chǎng)離奇詭異怠惶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)轧粟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)策治,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)脓魏,“玉大人,你說(shuō)我怎么就攤上這事通惫∶瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵履腋,是天一觀的道長(zhǎng)珊燎。 經(jīng)常有香客問(wèn)我,道長(zhǎng)遵湖,這世上最難降的妖魔是什么悔政? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮延旧,結(jié)果婚禮上谋国,老公的妹妹穿的比我還像新娘。我一直安慰自己迁沫,他們只是感情好芦瘾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著弯洗,像睡著了一般旅急。 火紅的嫁衣襯著肌膚如雪逢勾。 梳的紋絲不亂的頭發(fā)上牡整,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音溺拱,去河邊找鬼逃贝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛迫摔,可吹牛的內(nèi)容都是我干的沐扳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼句占,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沪摄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纱烘,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤杨拐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后擂啥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體哄陶,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年哺壶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屋吨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜒谤。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖至扰,靈堂內(nèi)的尸體忽然破棺而出鳍徽,到底是詐尸還是另有隱情,我是刑警寧澤敢课,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布旬盯,位于F島的核電站,受9級(jí)特大地震影響翎猛,放射性物質(zhì)發(fā)生泄漏胖翰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一切厘、第九天 我趴在偏房一處隱蔽的房頂上張望萨咳。 院中可真熱鬧,春花似錦疫稿、人聲如沸培他。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)舀凛。三九已至,卻和暖如春途蒋,著一層夾襖步出監(jiān)牢的瞬間猛遍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工号坡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留懊烤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓宽堆,卻偏偏與公主長(zhǎng)得像腌紧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畜隶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容