À partir d'un wrapper de propriété dans Swift, quelqu'un peut-il se référer à l'instance de la classe ou a frappé qui possède la propriété encapsulée? L'utilisation de self
ne fonctionne évidemment pas, pas plus que super
.
J'ai essayé de passer self
à la fonction init()
du wrapper de propriété, mais cela ne fonctionne pas non plus car self
sur Configuration
n'est pas encore défini lorsque @propertywrapper
est évalué.
Mon cas d'utilisation est dans une classe pour gérer un grand nombre de paramètres ou de configurations. Si une propriété est modifiée, je veux simplement informer les parties intéressées que quelque chose a changé. Ils n'ont pas vraiment besoin de savoir quelle valeur, alors utilisez quelque chose comme KVO
ou Publisher
pour chaque propriété n'est pas vraiment nécessaire.
Un wrapper de propriété semble idéal, mais je ne peux pas comprendre comment transmettre une sorte de référence à l'instance propriétaire à laquelle le wrapper peut rappeler.
Références:
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...
}
}
La réponse est non, ce n'est pas possible avec la spécification actuelle.
Je voulais faire quelque chose de similaire. Le mieux que j'ai pu trouver était d'utiliser la réflexion dans une fonction à la fin de init(...)
. Au moins de cette façon, vous pouvez annoter vos types et n'ajouter qu'un seul appel de fonction dans 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
}
}
}
}
Mes expériences basées sur: https://github.com/Apple/Swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper -type
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
Vous ne pouvez pas le faire hors de la boîte actuellement.
Cependant, la proposition à laquelle vous vous référez en discute comme une orientation future dans la dernière version: https://github.com/Apple/Swift-evolution/blob/master/proposals/0258-property-wrappers.md# référencement-le-type-enveloppant-auto-enveloppant
Pour l'instant, vous pouvez utiliser un projectedValue
pour affecter self
à. Vous pouvez ensuite l'utiliser pour déclencher une action après avoir défini le wrappedValue
.
Par exemple:
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)