Hi everyone,
I was watching the "Code-along: Add persistence with SwiftData" session and noticed a strange architectural choice at the end. They track model side-effects directly inside a SwiftUI View's initializer like this:
init(activity: Activity, isLast: Bool, isEditing: Bool) {
activity.token = withContinuousObservation(options: .didSet) { event in
// ... side effects here
}
}
This feels like a significant architectural smell. SwiftUI views are transient structures with no guaranteed lifetime—they can be initialized dozens of times a second during standard layout passes. Furthermore, if multiple views display or interact with the same Activity, this tracking work gets duplicated redundantly.
I understand this is a workaround because attaching a standard didSet directly to a stored property inside a @Model class doesn't trigger cleanly due to how the macro expands back-end storage.
To keep this data-logic in the model layer where it belongs, I came up with an alternative that maps a custom computed property over a real stored attribute using. Here is the pattern:
import SwiftUI
import SwiftData
@Model
class Item {
// 1. Persist the actual database column under an internal property name
private var _title: String
// 2. Expose a public computed property to intercept mutations
var title: String {
get {
_title
}
set {
// Updating the backing variable automatically fires the macro's observation hooks
_title = newValue
updatedAt = .now // Our derived side-effect!
}
}
var updatedAt: Date
init(title: String) {
self._title = title
self.updatedAt = .now
}
}
Why I prefer this over the WWDC approach:
Separation of Concerns: The model handles its own data dependencies (updatedAt), meaning the View layer remains purely declarative.
Predictable Execution: The mutation logic runs exactly once per write, regardless of how many views are rendering or re-initializing around the object.
No Manual Observation Setup: Because _title is a real, macro-backed attribute, SwiftData’s generated access and withMutation hooks are invoked naturally when the computed property reads or writes to it. We don't have to manually manage tokens or observation blocks.
What do you all think? Are there any hidden gotchas to manipulating the schema mapping via originalName like this, or is this a vastly superior layout to WWDC's view-bound observation snippet?
The downside is now the SQLIte column is _TITLE instead of TITLE. Is there any workaround for that? There doesn't seem to be @Attribute(columnName: "title")