Question 6
Single source of truth & duplicated state
Users see different counts on the list and detail screens after an edit. Architecturally, how do you prevent multiple ‘caches of truth’ for the same entity?
Follow-ups
- What about SwiftUI @State vs shared store?
Answer outline
This happens when the same entity is held in multiple mutable places — a list screen caches one copy, the detail screen another, and writes to one don't reach the other.
The fix is a single source of truth: one place owns the entity, all screens read from it, and all writes go through it.
A helpful mental model: separate canonical state from view-local state. Canonical state is the shared model that must stay consistent across screens — a post count, a user profile, a cart total. View-local state is scoped to one view and temporary — draft text, loading flags, sheet visibility.
Once an edit is committed it should update the shared store; every view then derives its display from that store rather than holding its own copy.
Principles
- Store each entity once — every screen reads from the same source, never a local copy.
- Route all writes through the store; views never mutate shared data directly.
- Separate canonical state (shared, must be consistent) from view-local state (ephemeral, scoped to one view).
@Stateis right for view-local ephemeral state; cross-screen entities belong in a shared observable store.
The store owns the array; views call update() rather than mutating their own copies:
@Observable
final class PostStore {
private(set) var posts: [Post] = []
func update(_ post: Post) {
guard let index = posts.firstIndex(where: { $0.id == post.id })
else { return }
posts[index] = post // one write; every observer sees it
}
}
Both views receive the same PostStore instance; neither holds its own copy of the data:
struct PostListView: View {
let store: PostStore
var body: some View {
List(store.posts) { PostRow(post: $0) }
}
}
struct PostDetailView: View {
let store: PostStore
let postId: Post.ID
var post: Post? { store.posts.first { $0.id == postId } }
var body: some View {
Text("Likes: \(post?.likeCount ?? 0)")
Button("Like") {
if var p = post {
p.likeCount += 1
store.update(p) // list view updates automatically
}
}
}
}
Follow-up angles
@Stateis the right fit for sheet visibility, draft text fields, and selection — state that lives and dies with one view and doesn't need to be consistent elsewhere.- For persistence-backed entities, let SwiftData or Core Data be the store and query from there — view models should not cache results in separate properties.