使用關聯物件實現的協議擴充套件中的屬性
在 Swift 中,協議擴充套件不能具有真正的屬性。
但是,在實踐中,你可以使用關聯物件技術。結果幾乎就像一個真正的屬性。
以下是向協議擴充套件新增關聯物件的確切技術:
從根本上說,你使用 objective-c“objc_getAssociatedObject”和_set 呼叫。
基本呼叫是:
get {
return objc_getAssociatedObject(self, & _Handle) as! YourType
}
set {
objc_setAssociatedObject(self, & _Handle, newValue, .OBJC_ASSOCIATION_RETAIN)
}
這是一個完整的例子。兩個關鍵點是:
-
在協議中,你必須使用“:class”來避免突變問題。
-
在擴充套件中,你必須使用“where self:UIViewController”(或任何適當的類)來提供確認型別。
因此,對於示例屬性 p
:
import Foundation
import UIKit
import ObjectiveC // don't forget this
var _Handle: UInt8 = 42 // it can be any value
protocol Able: class {
var click:UIView? { get set }
var x:CGFloat? { get set }
// note that you >> do not << declare p here
}
extension Able where Self:UIViewController {
var p:YourType { // YourType might be, say, an Enum
get {
return objc_getAssociatedObject(self, & _Handle) as! YourType
// HOWEVER, SEE BELOW
}
set {
objc_setAssociatedObject(self, & _Handle, newValue, .OBJC_ASSOCIATION_RETAIN)
// often, you'll want to run some sort of "setter" here...
__setter()
}
}
func __setter() { something = p.blah() }
func someOtherExtensionFunction() { p.blah() }
// it's ok to use "p" inside other extension functions,
// and you can use p anywhere in the conforming class
}
在任何符合要求的類中,你現在已經新增了屬性 p
:
你可以像使用符合類中的任何普通屬性一樣使用 p
。例:
class Clock:UIViewController, Able {
var u:Int = 0
func blah() {
u = ...
... = u
// use "p" as you would any normal property
p = ...
... = p
}
override func viewDidLoad() {
super.viewDidLoad()
pm = .none // "p" MUST be "initialized" somewhere in Clock
}
}
注意。你必須初始化偽屬性
Xcode 中會不會強制你在貼合類初始化 P
。
初始化 p
至關重要,可能在確認類的 viewDidLoad 中。
值得記住的是,p 實際上只是一個計算屬性。p 實際上只是兩個函式,帶有語法糖。在任何地方都沒有 p變數:編譯器在任何意義上都不“為 p 分配一些記憶體”。因此,期望 Xcode 強制執行“初始化 p”是沒有意義的。
實際上,為了更準確地說,你必須記住“第一次使用 p,就好像你正在初始化它”。 (同樣,這很可能在你的 viewDidLoad 程式碼中。)
關於這樣的 getter。
請注意,如果在設定 p
的值之前呼叫 getter,它將崩潰。
為避免這種情況,請考慮以下程式碼:
get {
let g = objc_getAssociatedObject(self, &_Handle)
if (g == nil) {
objc_setAssociatedObject(self, &_Handle, _default initial value_, .OBJC_ASSOCIATION)
return _default initial value_
}
return objc_getAssociatedObject(self, &_Handle) as! YourType
}
重複。Xcode 中會不會強制你在貼合類初始化頁。初始化 p 是必要的,比如在符合類的 viewDidLoad 中。
使程式碼更簡單……
你可能希望使用這兩個全域性函式:
func _aoGet(_ ss: Any!, _ handlePointer: UnsafeRawPointer!, _ safeValue: Any!)->Any! {
let g = objc_getAssociatedObject(ss, handlePointer)
if (g == nil) {
objc_setAssociatedObject(ss, handlePointer, safeValue, .OBJC_ASSOCIATION_RETAIN)
return safeValue
}
return objc_getAssociatedObject(ss, handlePointer)
}
func _aoSet(_ ss: Any!, _ handlePointer: UnsafeRawPointer!, _ val: Any!) {
objc_setAssociatedObject(ss, handlePointer, val, .OBJC_ASSOCIATION_RETAIN)
}
請注意,除了儲存型別之外,它們不執行任何操作,並使程式碼更具可讀性。 (它們本質上是巨集或行內函數。)
然後你的程式碼變成:
protocol PMable: class {
var click:UILabel? { get set } // ordinary properties here
}
var _pHandle: UInt8 = 321
extension PMable where Self:UIViewController {
var p:P {
get {
return _aoGet(self, &_pHandle, P() ) as! P
}
set {
_aoSet(self, &_pHandle, newValue)
__pmSetter()
}
}
func __pmSetter() {
click!.text = String(p)
}
func someFunction() {
p.blah()
}
}
(在_aoGet 的示例中,P 是初始化的:而不是 P()
,你可以使用“”,0 或任何預設值。)