Есть ли способ получить доступ к включающему экземпляру ObservableObject для вызова objectWillChange.send() из любого места оболочки свойства?

Я пытаюсь создать обертку свойств, похожую наCombine'sPublishedone(для нужд моего проекта), но с возможностью изменять обернутое свойство, отправляя значение издателю, хранящееся вprojectedValue, так:

      // in class
@PublishedMutable var foo = "foo"

$foo.send("bar")
// ...

Вот код оболочки свойства:

      @propertyWrapper
struct PublishedMutable<Value> {
    static subscript<T: ObservableObject>(
        _enclosingInstance instance: T,
        wrapped wrappedKeyPath: ReferenceWritableKeyPath<T, Value>,
        storage storageKeyPath: ReferenceWritableKeyPath<T, Self>
    ) -> Value {
        get {
            instance[keyPath: storageKeyPath].storage
        }
        set {
            let publisher = instance.objectWillChange
            // This assumption is definitely not safe to make in
            // production code, but it's fine for this demo purpose:
            (publisher as? ObservableObjectPublisher)?.send()
            
            instance[keyPath: storageKeyPath].storage = newValue
        }
    }
    
    @available(*, unavailable,
                message: "@PublishedMutable can only be applied to classes"
    )
    var wrappedValue: Value {
        get { fatalError() }
        set { fatalError() }
    }
    
    private var storage: Value{
        get { publisher.value }
        set { publisher.send(newValue) }
    }
    typealias Publisher = CurrentValueSubject<Value, Never>

    var projectedValue: Publisher { self.publisher }

    private var publisher: Publisher
    init(initialValue: Value) {
        self.publisher = Publisher(initialValue)
    }
    
    init(wrappedValue: Value) {
        self.init(initialValue: wrappedValue)
    }
}

А вот код игровой площадки, который я использовал для тестирования ее функций:

      class AmogusComic: ObservableObject {
    @PublishedMutable var currentPageContent = "S O S"
    
    private var cancellables: Set<AnyCancellable> = []
    
    func connect() {
        $currentPageContent.sink { newPage in
            print(newPage)
        }
        .store(in: &cancellables)
        objectWillChange.sink { _ in
            print("Update")
        }
        .store(in: &cancellables)
    }
}

let amogusComic = AmogusComic()
DispatchQueue.main.async {
    // connect publishers and print initial content. prints "S O S"
    amogusComic.connect()
    
    // change the value of current page via setting a property, the amogusComic class will reactively print "S U S" after
    amogusComic.currentPageContent = "S U S"
    
    // take a property publisher and send new value to publisher, the amogusComic class will reactively print "A M O G U S" after
    amogusComic.$currentPageContent.send("A M O G U S")
}

PlaygroundPage.current.needsIndefiniteExecution = true

Простое изменение значения путем непосредственного изменения свойства и отправки значения издателю работает отлично. Но я также хочу позвонитьobjectWillChange.send()каждый раз, когда значение меняется. Проблема в том, что оно вызывается только при непосредственном изменении обернутого свойства, что является неправильным поведением.

Тестовый код выводит это

      S O S
Update
S U S
A M O G U S

когда я думаю, что это должно вывести это

      S O S
Update
S U S
Update
A M O G U S

Как вы можете видеть в приведенном выше коде, я использовал статический индекс для получения и вызова, но оказывается, что Swift вызывает этот индекс только при непосредственном изменении свойства (amogusComic.currentPageContent = "S U S")

ПолучающийMirror(reflecting: self).superclassMirrorтоже не работает,superclassMirrorвсегда возвращает ноль

Могу ли я как-нибудь получитьObservableObjectвне_enclosingInstanceстатический индекс? Чтобы я мог позвонитьobjectWillChangeтакже при отправке значения издателю

1 ответ

Нет. У нас нет доступа к статическому индексному эквиваленту . Это секрет Apple: как подписывается на родительский объектobjectWillChange, без необходимости сначала вызывать его геттер или сеттер.

Вы можете только воссоздать (и вы это сделали)Published'sprojectedValueесли класс, с которым вы его используете, не является ObservableObject.

Тем не менее, в этом нет необходимости, если вы намерены простоsendметод.

      final class Object: ObservableObject {
  @Published var published = ""
}

let object = Object()
object.objectWillChange.sink { print("objectWillChange") }
object.$published.send("new value")
// "objectWillChange"
object.published // "new value"
      public extension Published.Publisher {
  mutating func send(_ value: Value) {
    Just(value).assign(to: &self)
  }
}