使用关联对象实现的协议扩展中的属性
在 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 或任何默认值。)