web-dev-qa-db-ja.com

Swift Property Wrapperは、ラッピングするプロパティの所有者を参照できますか?

Swiftのプロパティラッパー内から、誰かがクラスのインスタンスを参照したり、ラップされているプロパティを所有しているストライクをしたりできますか? selfを使用しても、明らかに機能せず、superも機能しません。

selfをプロパティラッパーのinit()に渡そうとしましたが、selfConfigurationがまだ定義されていないため、これも機能しません@propertywrapperが評価されます。

私の使用例は、多数の設定または構成を管理するクラスです。プロパティが変更された場合は、関係者にsomethingが変更されたことを通知します。実際にどの値を知っている必要はないので、各プロパティにKVOPublisherのようなものを使用する必要はありません。

プロパティラッパーは理想的に見えますが、ラッパーがコールバックできる所有インスタンスへの参照のようなものを渡す方法がわかりません。

参照:

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

答えはノーです。現在の仕様では不可能です。

似たようなことをしたかった。私が思いつくことができる最高のものは、init(...)の最後にある関数でリフレクションを使用することでした。少なくともこの方法で、タイプに注釈を付け、init()に1つの関数呼び出しのみを追加できます。


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

私の実験は次に基づいています: 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

現在、これをそのまま使用することはできません。

ただし、あなたが参照する提案では、これを最新バージョンの将来の方向性として説明しています: https://github.com/Apple/Swift-evolution/blob/master/proposals/0258-property-wrappers.md# reference-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)
0
tdekker