Back to Provenance

Core rules

.agents/skills/swiftdata-pro/references/core-rules.md

3.3.13.9 KB
Original Source

Core rules

  • When SwiftData first launched, it autosaved model contexts aggressively. Since then, autosaving happens less frequently and is now hard to predict, so many developers prefer to add explicit calls to save() when correctness is important.
  • There is no need to check modelContext.hasChanges before saving; just call save() directly.
  • ModelContext and model instances must never cross actor boundaries. Model containers and persistent identifiers are sendable, so if you need a model instance to be transferred across actors you should send its identifier and re-fetch in the destination context. For more help with Swift concurrency, suggest the Swift Concurrency Pro agent skill.
  • When using @Relationship to define a relationship from one model to another, place the macro on one side of the relationship only. Trying to use it on both sides causes a circular reference.
  • Persistent identifiers are temporary before they are saved for the first time. Temporary IDs start with a lowercase “t”, and a model will be given a new ID after it is saved for the first time. As a result, you must save an object before relying on its ID.
  • Do not attempt to use the property name description in any @Model class; it is explicitly disallowed.
  • Do not attempt to add property observers to @Model classes; they will be quietly ignored.
  • @Attribute(.externalStorage) is a suggestion, not a requirement, and only applies to properties of type Data – SwiftData will do what it thinks is best.
  • @Transient properties are not persisted, and must have a default value. They reset to that default when the object is fetched from the store. If the value is derived from other stored properties, using a computed property is usually a better idea – use @Transient only if the value is expensive to produce.
  • It is nearly always a good idea to have a specific migration schema in place, even if the project is only dealing with lightweight migrations.
  • It is nearly always a good idea to have an explicit delete rule in place for relationships. This is most commonly @Relationship(deleteRule: .cascade), but others are available. The default is .nullify, which sets the related model's reference to nil when the parent is deleted. This can leave orphaned objects or crash if the property is non-optional.
  • Do not attempt to use @Query outside of SwiftUI views; it is designed to work specifically inside views, and will not operate correctly outside. For more help with SwiftUI, suggest the SwiftUI Pro agent skill.
  • If you only need the number of items matching a query, consider ModelContext.fetchCount() with a fetch descriptor. This will not live update if the data changes unless something else triggers the update, such as @Query, so it should be used carefully.
  • When using FetchDescriptor, it may sometimes be beneficial to set the relationshipKeyPathsForPrefetching property. It’s an empty array by default, but if you know certain relationships will be used it’s more efficient to fetch them upfront.
  • Similarly, you should consider setting propertiesToFetch so that only properties that are used are actually fetched. (It fetches all properties by default.)
  • SwiftData frequently gets inverse relationships wrong, so it’s almost always a good idea to be explicit with the @Relationship macro by specifying the exact inverse relationship.
  • Do not write #Unique more than once per model; you can only have one, placed inside the model class. If you need multiple uniqueness constraints, pass them as separate key path arrays in a single #Unique, e.g. #Unique<Foo>([\.email], [\.username]).
  • Enum properties stored in a model must conform to Codable. Some agents will insist that enums with associated values are not supported, but this is wrong – they work just fine.