Swift快速為類擴充套件屬性

sunflowerseat發表於2018-01-27

在程式編寫過程中,我們常常需要為已有的類擴充套件新的屬性。通常我們的解決辦法是先宣告一個Key,然後使用objc_getAssociatedObjectobjc_setAssociatedObject來設定屬性。相對來說比較麻煩,因為擴充套件屬性的需求比較大,所以筆者對這兩個方法做了一些封裝,減少了很多程式碼。

使用

首先我們來看看封裝後如何使用。

  • 把Property.swift拖到你的專案中
  • 讓類/Protocol 繼承 Property
  • 宣告你的屬性,get/set參照如下程式碼
extension View:Property{
    var margin : Int{
        get{ return get0() }
        set{ set0(newValue)}
    }
}

是不是非常簡單?不過在使用這個Property之前,一定要看清楚注意事項哦。
Property裡面預設封裝了設定三個屬性的方法。
擴充套件前三個屬性的時候分別是 get0() & set0()、get1() & set1()、get2() & set2()
那麼超過三個屬性應該如何設定呢?

  • 方案1:擴充套件Property的方法。
  • 方案2:使用Property預設的get() set(),並且需要傳入一個變數指標,參考如下程式碼:
    var test : String{
        get{ return get(&keyPoint) }
        set{ set(&keyPoint, newValue)}
    }

也還是比較簡單的,畢竟為一個類擴充套件超過三個以上的屬性的需求還是比較小的。

封裝過程

首先我們看看,擴充套件屬性通常使用的程式碼

struct XKeys {
    static var common : String = "common"
}
extension AbstractProtocol{
    var common : String{
        get{
            return objc_getAssociatedObject(self, &XKeys.common) as! String
        }
        set{
            objc_setAssociatedObject(self, &XKeys.common, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

在複製貼上了多次這樣的程式碼之後,我實在厭倦了這樣擴充套件屬性的方式,然後開始了自己的封裝。
首先我發現宣告一個Key之後,可以給多個類共用,沒有任何影響,但是如果同一個類的不同屬性,使用了相同的Key,就會有問題了。所以首先要保證同一個類,擴充套件出來的不同屬性的key值必須要不同。
所以我想到用一個陣列來儲存key,不過很可惜失敗了。

最開始封裝Property的時候是直接宣告瞭一個類,寫了一些靜態方法。然後在get set中呼叫。

class Property2 {
    static func get<T : Any>(_ key: UnsafeRawPointer) -> T{
        return objc_getAssociatedObject(self, key) as! T
    }
    static func set<T : Any>(_ key: UnsafeRawPointer,_ newValue : T) {
        objc_setAssociatedObject(self, key, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

不過測試過程中發現一些問題,這個self實際應該使用被擴充套件的物件的類的self,所以經過修改後,程式碼如下:

class Property2 {
    static func get<T : Any>(_ o : Any, _ key: UnsafeRawPointer) -> T{
        return objc_getAssociatedObject(o, key) as! T
    }
    static func set<T : Any>(_ o : Any, _ key: UnsafeRawPointer,_ newValue : T) {
        objc_setAssociatedObject(o, key, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

呼叫的時候:

var common : String{
    get{
        return Property2.get(self, &XKeys.common)
    }
    set{
        Property2.set(self, &XKeys.common, newValue)
    }
}

感覺封裝了跟沒封裝基本差不多啊。
有沒有辦法能省略Property和self呢,於是我想到了Protocol,然後讓類去繼承我的Property,這樣就可以省略掉這兩項了。不過Key還是要傳遞,所以我預設宣告瞭三個key,再提供一個需要傳遞key的方法。最後封裝好的程式碼為:

//  Property.swift
//
//  Created by Fancy on 26/1/18.
//  Copyright © 2018年 Artifex Software, Inc. All rights reserved.

import UIKit
struct PropertyKey{
    static var key0 : Void?
    static var key1 : Void?
    static var key2 : Void?
}

protocol Property{}
extension Property{
    func get<T : Any>(_ key: UnsafeRawPointer) -> T{
        return objc_getAssociatedObject(self, key) as! T
    }
    func set<T : Any>(_ key: UnsafeRawPointer,_ newValue : T) {
        objc_setAssociatedObject(self, key, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    func get0<T : Any>() -> T{
        return objc_getAssociatedObject(self, &PropertyKey.key0) as! T
    }
    func set0<T : Any>(_ newValue : T) {
        objc_setAssociatedObject(self, &PropertyKey.key0, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    func get1<T : Any>() -> T{
        return objc_getAssociatedObject(self, &PropertyKey.key1) as! T
    }
    func set1<T : Any>(_ newValue : T) {
        objc_setAssociatedObject(self, &PropertyKey.key1, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    func get2<T : Any>() -> T{
        return objc_getAssociatedObject(self, &PropertyKey.key2) as! T
    }
    func set2<T : Any>(_ newValue : T) {
        objc_setAssociatedObject(self, &PropertyKey.key2, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

結語

整個封裝過程沒有什麼高科技含量的操作,寫文章做點記錄,希望能給到需要的人幫助。

相關文章