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

前端 未结 4 2042
说谎
说谎 2021-02-07 01:00

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

相关标签:
4条回答
  • 2021-02-07 01:21

    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)
    
    0 讨论(0)
  • 2021-02-07 01:38

    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
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-07 01:43

    The answer is yes! See this answer

    Example code for calling ObservableObject publisher with a UserDefaults wrapper:

    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)
        }
      }
    }
    
    0 讨论(0)
  • 2021-02-07 01:44

    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
    
    0 讨论(0)
提交回复
热议问题