Can a Swift Property Wrapper reference the owner of the property its wrapping?

人盡茶涼 提交于 2020-01-03 07:30:13

问题


From within a property wrapper in Swift, can you someone refer back to the instance of the class or struck that owns the property being wrapped? Using self doesn't obviously work, nor does super.

I tried to pass in self to the property wrapper's init() but that doesn't work either because self on Configuration is not yet defined when @propertywrapper is evaluated.

My use case is in a class for managing a large number of settings or configurations. If any property is changed, I just want to notify interested parties that something changed. They don't really need to know which value just, so use something like KVO or a Publisher for each property isn't really necessary.

A property wrapper looks ideal, but I can't figure out how to pass in some sort of reference to the owning instance that the wrapper can call back to.

References:

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

回答1:


The answer is no, it's not possible with the current specification.

I wanted to do something similar. The best I could come up with was to use reflection in a function at the end of init(...). At least this way you can annotate your types and only add a single function call in 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
            }
        }
    }
}



回答2:


My experiments based on : 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



回答3:


You cannot do this out of the box currently.

However, the proposal you refer to discusses this as a future direction in the latest version: https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type

For now, you would be able to use a projectedValue to assign self to. You could then use that to trigger some action after setting the wrappedValue.

As an example:

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)


来源:https://stackoverflow.com/questions/56686026/can-a-swift-property-wrapper-reference-the-owner-of-the-property-its-wrapping

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!