Swift 属性 包装器可以引用 属性 其包装的所有者吗?
Can a Swift Property Wrapper reference the owner of the property its wrapping?
在 Swift 的 属性 包装器中,有人可以回顾 class 的实例或拥有被包装的 属性 的打击吗?使用 self
显然行不通,super
也行不通。
我试图将 self
传递给 属性 包装器的 init()
但这也不起作用,因为 self
on Configuration
还没有在评估 @propertywrapper
时定义。
我的用例是 class 用于管理大量设置或配置。如果有任何 属性 发生了变化,我只是想通知感兴趣的人 某事 发生了变化。他们真的不需要知道哪个值,所以对每个 属性 使用 KVO
或 Publisher
之类的东西并不是真正必要的。
属性 包装器看起来很理想,但我不知道如何将某种引用传递给包装器可以回调到的拥有实例。
参考文献:
enum PropertyIdentifier {
case backgroundColor
case textColor
}
@propertyWrapper
struct Recorded<T> {
let identifier:PropertyIdentifier
var _value: T
init(_ identifier:PropertyIdentifier, defaultValue: T) {
self.identifier = identifier
self._value = defaultValue
}
var value: T {
get { _value }
set {
_value = newValue
// How to callback to Configuration.propertyWasSet()?
//
// [self/super/...].propertyWasSet(identifier)
}
}
}
struct Configuration {
@Recorded(.backgroundColor, defaultValue:NSColor.white)
var backgroundColor:NSColor
@Recorded(.textColor, defaultValue:NSColor.black)
var textColor:NSColor
func propertyWasSet(_ identifier:PropertyIdentifier) {
// Do something...
}
}
答案是否定的,目前的规范是不可能的。
我想做类似的事情。我能想到的最好办法是在 init(...)
末尾的函数中使用反射。至少这样你可以注释你的类型并且只在 init()
.
中添加一个函数调用
fileprivate protocol BindableObjectPropertySettable {
var didSet: () -> Void { get set }
}
@propertyDelegate
class BindableObjectProperty<T>: BindableObjectPropertySettable {
var value: T {
didSet {
self.didSet()
}
}
var didSet: () -> Void = { }
init(initialValue: T) {
self.value = initialValue
}
}
extension BindableObject {
// Call this at the end of init() after calling super
func bindProperties(_ didSet: @escaping () -> Void) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? BindableObjectPropertySettable {
child.didSet = didSet
}
}
}
}
您目前无法开箱即用。
但是,您提到的提案在最新版本中将此作为未来的方向进行了讨论:
https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
目前,您可以使用 projectedValue
将 self
分配给。
然后,您可以在设置 wrappedValue
.
后使用它来触发某些操作
举个例子:
import Foundation
@propertyWrapper
class Wrapper {
let name : String
var value = 0
weak var owner : Owner?
init(_ name: String) {
self.name = name
}
var wrappedValue : Int {
get { value }
set {
value = 0
owner?.wrapperDidSet(name: name)
}
}
var projectedValue : Wrapper {
self
}
}
class Owner {
@Wrapper("a") var a : Int
@Wrapper("b") var b : Int
init() {
$a.owner = self
$b.owner = self
}
func wrapperDidSet(name: String) {
print("WrapperDidSet(\(name))")
}
}
var owner = Owner()
owner.a = 4 // Prints: WrapperDidSet(a)
protocol Observer: AnyObject {
func observableValueDidChange<T>(newValue: T)
}
@propertyWrapper
public struct Observable<T: Equatable> {
public var stored: T
weak var observer: Observer?
init(wrappedValue: T, observer: Observer?) {
self.stored = wrappedValue
}
public var wrappedValue: T {
get { return stored }
set {
if newValue != stored {
observer?.observableValueDidChange(newValue: newValue)
}
stored = newValue
}
}
}
class testClass: Observer {
@Observable(observer: nil) var some: Int = 2
func observableValueDidChange<T>(newValue: T) {
print("lol")
}
init(){
_some.observer = self
}
}
let a = testClass()
a.some = 4
a.some = 6
答案是肯定的!参见
使用 UserDefaults 包装器调用 ObservableObject 发布者的示例代码:
import Combine
import Foundation
class LocalSettings: ObservableObject {
static var shared = LocalSettings()
@Setting(key: "TabSelection")
var tabSelection: Int = 0
}
@propertyWrapper
struct Setting<T> {
private let key: String
private let defaultValue: T
init(wrappedValue value: T, key: String) {
self.key = key
self.defaultValue = value
}
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
public static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, T>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Setting<T>>
) -> T {
get {
return object[keyPath: storageKeyPath].wrappedValue
}
set {
(object.objectWillChange as? ObservableObjectPublisher)?.send()
UserDefaults.standard.set(newValue, forKey: object[keyPath: storageKeyPath].key)
}
}
}
在 Swift 的 属性 包装器中,有人可以回顾 class 的实例或拥有被包装的 属性 的打击吗?使用 self
显然行不通,super
也行不通。
我试图将 self
传递给 属性 包装器的 init()
但这也不起作用,因为 self
on Configuration
还没有在评估 @propertywrapper
时定义。
我的用例是 class 用于管理大量设置或配置。如果有任何 属性 发生了变化,我只是想通知感兴趣的人 某事 发生了变化。他们真的不需要知道哪个值,所以对每个 属性 使用 KVO
或 Publisher
之类的东西并不是真正必要的。
属性 包装器看起来很理想,但我不知道如何将某种引用传递给包装器可以回调到的拥有实例。
参考文献:
enum PropertyIdentifier {
case backgroundColor
case textColor
}
@propertyWrapper
struct Recorded<T> {
let identifier:PropertyIdentifier
var _value: T
init(_ identifier:PropertyIdentifier, defaultValue: T) {
self.identifier = identifier
self._value = defaultValue
}
var value: T {
get { _value }
set {
_value = newValue
// How to callback to Configuration.propertyWasSet()?
//
// [self/super/...].propertyWasSet(identifier)
}
}
}
struct Configuration {
@Recorded(.backgroundColor, defaultValue:NSColor.white)
var backgroundColor:NSColor
@Recorded(.textColor, defaultValue:NSColor.black)
var textColor:NSColor
func propertyWasSet(_ identifier:PropertyIdentifier) {
// Do something...
}
}
答案是否定的,目前的规范是不可能的。
我想做类似的事情。我能想到的最好办法是在 init(...)
末尾的函数中使用反射。至少这样你可以注释你的类型并且只在 init()
.
fileprivate protocol BindableObjectPropertySettable {
var didSet: () -> Void { get set }
}
@propertyDelegate
class BindableObjectProperty<T>: BindableObjectPropertySettable {
var value: T {
didSet {
self.didSet()
}
}
var didSet: () -> Void = { }
init(initialValue: T) {
self.value = initialValue
}
}
extension BindableObject {
// Call this at the end of init() after calling super
func bindProperties(_ didSet: @escaping () -> Void) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? BindableObjectPropertySettable {
child.didSet = didSet
}
}
}
}
您目前无法开箱即用。
但是,您提到的提案在最新版本中将此作为未来的方向进行了讨论: https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
目前,您可以使用 projectedValue
将 self
分配给。
然后,您可以在设置 wrappedValue
.
举个例子:
import Foundation
@propertyWrapper
class Wrapper {
let name : String
var value = 0
weak var owner : Owner?
init(_ name: String) {
self.name = name
}
var wrappedValue : Int {
get { value }
set {
value = 0
owner?.wrapperDidSet(name: name)
}
}
var projectedValue : Wrapper {
self
}
}
class Owner {
@Wrapper("a") var a : Int
@Wrapper("b") var b : Int
init() {
$a.owner = self
$b.owner = self
}
func wrapperDidSet(name: String) {
print("WrapperDidSet(\(name))")
}
}
var owner = Owner()
owner.a = 4 // Prints: WrapperDidSet(a)
protocol Observer: AnyObject {
func observableValueDidChange<T>(newValue: T)
}
@propertyWrapper
public struct Observable<T: Equatable> {
public var stored: T
weak var observer: Observer?
init(wrappedValue: T, observer: Observer?) {
self.stored = wrappedValue
}
public var wrappedValue: T {
get { return stored }
set {
if newValue != stored {
observer?.observableValueDidChange(newValue: newValue)
}
stored = newValue
}
}
}
class testClass: Observer {
@Observable(observer: nil) var some: Int = 2
func observableValueDidChange<T>(newValue: T) {
print("lol")
}
init(){
_some.observer = self
}
}
let a = testClass()
a.some = 4
a.some = 6
答案是肯定的!参见
使用 UserDefaults 包装器调用 ObservableObject 发布者的示例代码:
import Combine
import Foundation
class LocalSettings: ObservableObject {
static var shared = LocalSettings()
@Setting(key: "TabSelection")
var tabSelection: Int = 0
}
@propertyWrapper
struct Setting<T> {
private let key: String
private let defaultValue: T
init(wrappedValue value: T, key: String) {
self.key = key
self.defaultValue = value
}
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
public static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, T>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Setting<T>>
) -> T {
get {
return object[keyPath: storageKeyPath].wrappedValue
}
set {
(object.objectWillChange as? ObservableObjectPublisher)?.send()
UserDefaults.standard.set(newValue, forKey: object[keyPath: storageKeyPath].key)
}
}
}