Context
I am fetching numerous objects from a SwiftData retailer utilizing a frequently-changing predicate. Conventional @Question
setups didn’t present the pliability I needed (particularly for rendering loading states), so I created a background actor to deal with fetching the info:
@ModelActor
actor ThreadsafeBackgroundActor: Sendable {
func fetchData(_ predicate: Predicate? = nil) throws -> [CardView] {
let descriptor = if let p = predicate {
FetchDescriptor(predicate: p)
} else {
FetchDescriptor()
}
let playing cards = attempt context.fetch(descriptor)
return playing cards.map(CardView.init)
}
}
I’ve additionally acquired a view mannequin calling the actor:
@Observable
class CardListViewModel {
enum State {
case idle
case loading
case failed(Error)
case loaded([CardView])
}
personal(set) var state = State.idle
func fetchData(container: ModelContainer, predicate: Predicate) async throws -> [CardView] {
let service = ThreadsafeBackgroundActor(modelContainer: container)
return attempt await service.fetchData(predicate)
}
@MainActor func load(container: ModelContainer, filter: CardPredicate) async {
state = .loading
do {
let playing cards = attempt await fetchData(container: container, predicate: filter.predicate)
state = .loaded(playing cards)
} catch is CancellationError {
state = .idle
} catch {
state = .failed(error)
}
}
}
And I’ve acquired a process
on my SwiftUI view to kick off the preliminary load:
.process(id: cardFilter) { // Reloads at any time when the cardboard filter modifications! Good!
viewModel.load(container: context.container, filter: cardFilter)
}
This setup works excellently till something within the database modifications. Database updates (inserts, modifications, deletes) don’t set off my load
operate, even after calling context.save()
.
How can I make my load
operate re-run at any time when the database modifications?
Makes an attempt and Analysis
-
I’ve tried a brute-force route of introducing a
reloadCount
state variable, handed round wherever wanted. Areas of the code that replace the database incrementreloadCount
, and a separate process in my record view watches the rely:.process(id: cardFilter) { /* Similar name */ } .process(id: reloadCount) { // Eww... viewModel.load(container: context.container, filter: cardFilter) }
Not solely is that this technique tedious and brittle, however it additionally runs the danger of calling
load
a number of occasions unnecessarily (particularly on the preliminary render time). -
I’ve seemed into Swift’s streaming notification system. I am pretty assured that
NSPersistentStoreRemoteChange
is what I need to watch. I simply can not determine how/the place to initialize that watcher.addObserver
asks for Goal-C annotations. I do not suppose that.writer().sink { }
is the answer both, as a result of I need to kick off the mutating nameviewModel.load()
within the (escaping) closure.