Swift在擴(kuò)展中關(guān)聯(lián)對(duì)象

原文在這里, 轉(zhuǎn)載請(qǐng)貼原文鏈接

Objective-C 最讓人詬病的也許就是不能給已有類(lèi)添加屬性, 但是可以通過(guò) Objective-C 的運(yùn)行時(shí)機(jī)制關(guān)聯(lián)自定義屬性到對(duì)象上, 幾乎彌補(bǔ)了這個(gè)痛點(diǎn).

Swift Extension 比 Objective-C Category 增色不少, extension 能夠給已有類(lèi)添加計(jì)算型屬性, 這已經(jīng)是很大的進(jìn)步, 但是仍然不能添加存儲(chǔ)屬性. Swift 中也可以使用 Objective-C runtime 的關(guān)聯(lián)對(duì)象(Associated Objects)的方式添加屬性, 彌補(bǔ)這一痛點(diǎn).

關(guān)聯(lián)對(duì)象(Associated Objects)

Swift 中提供三個(gè)與 Objective-C 類(lèi)似的方法將自定義的屬性關(guān)聯(lián)到對(duì)象上:

  1. objc_setAssociatedObject
  2. objc_getAssociatedObject
  3. objc_removeAssociatedObjects

注意: 使用 objc_removeAssociatedObjects 時(shí)要小心, 這個(gè)方法會(huì)刪除對(duì)象關(guān)聯(lián)的所有屬性, 就可能導(dǎo)致把別人添加的關(guān)聯(lián)屬性也刪掉. 如果要?jiǎng)h除某一個(gè)屬性, 使用 objc_setAssociatedObject 方法, value 置為 nil.

下面給 UIView 添加三種不同類(lèi)型的屬性: isShow, displayName, width.

extension UIView {
    // 嵌套結(jié)構(gòu)體
    private struct AssociatedKeys {
        static var isShowKey = "isShowKey"
        static var displayNameKey = "displayNameKey"
        static var widthKey = "widthKey"
    }
    
    // Bool 類(lèi)型
    var isShow: Bool {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.isShowKey) as! Bool
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.isShowKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    // String 類(lèi)型
    var displayName: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.displayNameKey) as? String
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.displayNameKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
    }
    
    // Float 類(lèi)型
    var width: Float {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.widthKey) as! Float
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.widthKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

上述列子說(shuō)明幾點(diǎn):

  • 嵌套私有結(jié)構(gòu)體, 聲明與擴(kuò)展屬性對(duì)應(yīng)的鍵(key). Swift Extension 提供了豐富的功能, 可以在 Extension 中嵌套類(lèi)型, 使用 private 私有訪問(wèn)控制, 不會(huì)污染整個(gè)命名空間, 而且能夠統(tǒng)一管理關(guān)聯(lián)對(duì)象鍵.
  • Swift 的基本類(lèi)型Int, Float, Double, Bool能夠自動(dòng)隱式地轉(zhuǎn)換成 Objective-C 的 NSNumber 類(lèi)型, 所以不需要顯示的包裝成 NSNumber 類(lèi)型進(jìn)行關(guān)聯(lián).
  • 如果使用 OBJC_ASSOCIATION_ASSIGN 關(guān)聯(lián)策略時(shí)要注意, 文檔中指出是弱引用, 但不完全等同于 weak, 更像是 unsafe_unretained 引用, 關(guān)聯(lián)對(duì)象被釋放后,關(guān)聯(lián)屬性仍然保留被釋放的地址, 如果不小心訪問(wèn)關(guān)聯(lián)屬性, 就會(huì)造成野指針訪問(wèn)出錯(cuò).

Specifies a weak reference to the associated object.

抽取關(guān)聯(lián)對(duì)象方法

我們可以把關(guān)聯(lián)對(duì)象的方法提取成公共方法, 在 NSObject 類(lèi)的 extension 里實(shí)現(xiàn), 只要繼承自 NSObject 的類(lèi)就能夠調(diào)用關(guān)聯(lián)對(duì)象方法, 通過(guò)Swift 泛型來(lái)關(guān)聯(lián)不同類(lèi)型的屬性.

extension NSObject {
    func setAssociated<T>(value: T, associatedKey: UnsafeRawPointer, policy: objc_AssociationPolicy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) -> Void {
        objc_setAssociatedObject(self, associatedKey, value, policy)
    }
    
    func getAssociated<T>(associatedKey: UnsafeRawPointer) -> T? {
        let value = objc_getAssociatedObject(self, associatedKey) as? T
        return value;
    }
}

我們只需要在 UIView+Extension.swift 中調(diào)動(dòng)上面兩個(gè)方法即可, 目前只支持有可選類(lèi)型的屬性.

extension UIView {
    private struct AssociatedKeys {
        static var displayNameKey = "displayNameKey"
    }
    
    var displayName: String? {
        get {
            return getAssociated(associatedKey: &AssociatedKeys.displayNameKey)
        }
        set {
            setAssociated(value: newValue, associatedKey: &AssociatedKeys.displayNameKey)
        }
    }
}   

關(guān)聯(lián)閉包屬性

開(kāi)發(fā)中有時(shí)會(huì)給已有類(lèi)關(guān)聯(lián)閉包屬性, 比如給 UIViewController 類(lèi)添加一個(gè) pushCompletion 的閉包屬性, 當(dāng)導(dǎo)航控制器 push 動(dòng)作完成后調(diào)用該控制器的 pushCompletion 閉包.
先按照最基本的方式來(lái)關(guān)聯(lián)對(duì)象, 如下:

typealias pushCompletionClosure = ()->()

extension UIViewController {
    private struct AssociatedKeys {
        static var pushCompletionKey = "pushCompletionKey"
    }
    
    var pushCompletion: pushCompletionClosure? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.pushCompletionKey) as? pushCompletionClosure
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.pushCompletionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
    }
}

開(kāi)開(kāi)心心編譯一發(fā), 發(fā)現(xiàn)編譯報(bào)錯(cuò):

關(guān)聯(lián)閉包報(bào)錯(cuò)

定位問(wèn)題發(fā)現(xiàn)出在 objc_setAssociatedObject 這個(gè)方法上. 原來(lái)閉包屬性需要包裝一下才能進(jìn)行關(guān)聯(lián), 下面給出兩種解決辦法:

  1. 使用泛型包裝閉包屬性, 利用 NSObject+Extension.swift 中的 setAssociated 方法來(lái)關(guān)聯(lián)閉包.
  2. 創(chuàng)建私有閉包容器類(lèi), 利用閉包容器間接關(guān)聯(lián)閉包屬性.

泛型包裝閉包屬性

setAssociated 方法需要泛型參數(shù), 當(dāng)傳入閉包后, 就會(huì)把閉包包裝成泛型.

set {
   setAssociated(value: newValue, associatedKey: &AssociatedKeys.pushCompletionKey)
}

閉包容器

使用閉包容器的方式關(guān)聯(lián)閉包屬性, 過(guò)程分為兩步:

  1. 在 extension 中嵌套創(chuàng)建容器類(lèi), 容器類(lèi)中定義需要關(guān)聯(lián)的閉包屬性.
  2. 關(guān)聯(lián)對(duì)象時(shí)把容器類(lèi)對(duì)象關(guān)聯(lián)到已有類(lèi), 間接的就把閉包屬性關(guān)聯(lián)到已有類(lèi).

閉包容器的方式是把閉包屬性包裝到了容器中, 再把容器對(duì)象關(guān)聯(lián)到已有類(lèi)上, 跟泛型包裝閉包有異曲同工之處, 因此必須通過(guò)容器對(duì)象來(lái)訪問(wèn)閉包, 如果需要給類(lèi)關(guān)聯(lián)的閉包屬性相對(duì)較多, 這種方式也不失為一種好方法, 能統(tǒng)一管理閉包屬性, 代碼層級(jí)結(jié)構(gòu)也比較清晰.

typealias pushCompletionClosure = ()->()

extension UIViewController {
    private struct AssociatedKeys {
        static var pushCompletionKey = "pushCompletionKey"
    }
    
    // 嵌套閉包容器類(lèi)
    class closureContainer {
        var pushCompletion: pushCompletionClosure?
    }
    
    // 關(guān)聯(lián)容器屬性
    var container: closureContainer? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.pushCompletionKey) as? closureContainer
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.pushCompletionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

代碼在這里:
github

歡迎大家留言斧正!

參考鏈接:
http://swift.gg/2016/10/11/swift-extensions-can-add-stored-properties/
http://stackoverflow.com/questions/24133058/is-there-a-way-to-set-associated-objects-in-swift/25428409#25428409

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容