A very common problem with observation is stopping it. The proposal includes a handleChanges
example:
@MainActor
func handleChanges(_ example: ChangesExample) {
Task { @MainActor in
for await change in example.changes(for: [\.someField, \.someOtherField]) {
...
}
}
}
I think this is exactly the pattern many people will reach for. How would this observation be cancelled? How can example
ever be released, and if this observation is part of another object (which is the norm), how would the observer ever be released?
A possible motivating example, would be an ViewModel that is observing another object, and is itself observable.
@MainActor
@Observable class ViewModel {
private var model: Model
// This is what the View will observe
public var filteredBooks: [String]
init(model: Model) {
self.model = model
// I've seen folks reach for this pattern often to update the local property.
// I'm not clear there's a better solution with the initial proposal since there
// are no nested keyPaths.
Task { @MainActor in
for await newBooks in model.books.values(for: \.books) {
filteredBooks = newBooks.filter { $0.first == "A" }
}
}
}
}
@Observable class Model {
var books: [String] = []
// ... Network/DB async stuff that update `books`...
}
Is there a more correct way to implement this under the current proposal? How can the Task ever be cancelled? What kind of [weak self]
dance if any is required here to avoid leaking memory?
As a general comment, I don't see a lot of "real-world-ish" examples in this proposal, other than the SwiftUI example which relies on unspecified changes to SwiftUI (which only Apple can do, and only Apple will know how it is done). Demonstrating how this would work for a small UIKit app that interacts with the network, and that handles errors, memory management, unit tests, and cancellation correctly, could go a long way to validating the design and give a clear example of how we should write our production code.