在程式編寫過程中,我們常常需要為已有的類擴充套件新的屬性。通常我們的解決辦法是先宣告一個Key,然後使用
objc_getAssociatedObject
和objc_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)
}
}
結語
整個封裝過程沒有什麼高科技含量的操作,寫文章做點記錄,希望能給到需要的人幫助。