有没有办法访问封闭实例“ObservableObject”以从属性包装器中的任何位置调用“objectWillChange.send()”

mrp*_*w69 7 swift combine property-wrapper property-wrapper-published swift-property-wrapper

我正在尝试制作一个类似于 的属性包装器CombinePublished满足我的项目需求),但能够通过向发布者发送存储在 中的值来修改包装的属性projectedValue,如下所示:

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

$foo.send("bar")
// ...
Run Code Online (Sandbox Code Playgroud)

这是属性包装器的代码:

@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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我用来测试其功能的游乐场代码:

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

Run Code Online (Sandbox Code Playgroud)

只需通过直接修改属性并将值发送给发布者来修改值即可完美完成。但我也想objectWillChange.send()在值更改时调用。问题是,只有在直接修改包装的属性时才会调用它,这是不正确的行为。

测试代码输出这个

S O S
Update
S U S
A M O G U S
Run Code Online (Sandbox Code Playgroud)

当我认为应该输出这个时

S O S
Update
S U S
Update
A M O G U S
Run Code Online (Sandbox Code Playgroud)

正如你在上面的代码中看到的,我使用_enclosingInstance静态下标来获取ObservableObject和调用objectWillChange,但事实证明,Swift 仅在直接修改属性时才调用该下标 ( amogusComic.currentPageContent = "S U S")

获取Mirror(reflecting: self).superclassMirror也不起作用,superclassMirror总是返回nil

我能以某种方式得到静态下标的ObservableObject外部吗?所以我也可以在向发布者发送值时_enclosingInstance调用objectWillChange

小智 2

不可以。我们无法访问相当于 的静态下标projectedValue。Apple 的秘密是,a 如何Published订阅其父对象的objectWillChange,而不需要先调用其 getter 或 setter。

如果您使用它的Published不是.projectedValueObservableObject

也就是说,如果您的目的只是想要一种方法,则没有必要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"
Run Code Online (Sandbox Code Playgroud)
public extension Published.Publisher {
  mutating func send(_ value: Value) {
    Just(value).assign(to: &self)
  }
}
Run Code Online (Sandbox Code Playgroud)