web-dev-qa-db-fra.com

Un Swift Property Wrapper peut-il référencer le propriétaire de la propriété de son habillage?

À 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:

SE-0258

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...
  }
}
18
kennyc

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
            }
        }
    }
}
7
arsenius

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
1
Robert Koval

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)
0
tdekker