SwiftData
Unofficial AI summary of the WWDC26 group lab. May contain mistakes.
What's the proper way to use SwiftData with widgets and app intents?
- For multiprocess coordination (widgets, app intents), designate one app process as the primary owner of the database and migration.
- Other processes can read and write to the same database file after the primary process has handled migrations.
- Avoid including schema migration plans in widgets and extensions; use the error path to prompt the user to open the main app for migration.
- The
SampleTripsapp includes a widget that utilizes the group container for data sharing.
How should developers approach large data sets in SwiftData?
- For a large number of rows, ensure correct indexing for fast database fetches.
- Limit fetch requests and memory usage to prevent out-of-memory errors.
- For very large data items (e.g., images, files), use the
externalStorageattribute to store data in separate files instead of directly in the database. - When ingesting large data sets, perform insertions in smaller chunks using batch insertion and short-lived model contexts for each batch.
- When using
Queryin SwiftUI with large datasets, limit fetches withfetchLimitand ensure predicates accurately express the desired data. Use Instruments (Hitches, Persistence) to identify performance bottlenecks.
What are the best practices for creating sample data for previews and testing?
- Use the
previewTraitsparameter to seed previews with expressive sample data that covers various UI scenarios. - Within previews, developers can use
Queryto drive the view with sample data. - For migration testing, build a corpus of previous store versions to ensure all previously shipped models can migrate successfully.
- Consider using agentic programming for generating diverse sample data instances.
When should a developer choose ResultsObserver over Query?
ResultsObserveris intended for observable fetching outside of a SwiftUI view, such as in a view model.- Both
ResultsObserverandQueryhave similar performance characteristics under the hood. ResultsObserversimplifies handling data updates by observing changes directly, removing the need for manual notification listening and re-fetching.HistoryObserver(part ofResultsObserver) can observe changes from a specific point in time and is useful for replication or keeping other systems up-to-date.- Filter
HistoryObserverto local writes only when replicating — ignore remote-server writes to avoid echo loops. ResultsObservercan be combined withObservableObjectin AppKit and UIKit to update non-SwiftUI views efficiently.
How are sums and averages handled in SwiftData without loading all data into RAM?
- SwiftData currently lacks direct equivalents to Core Data's
NSExpressionfor on-disk calculations. Data often needs to be loaded into RAM for such operations. - For min/max calculations, a fetch with
fetchLimitof one and a sort descriptor can be used. - File feedback reports with specific use cases are encouraged to drive future API design for these types of calculations.
- Consider using coexistence: a Core Data stack and a SwiftData stack can point to the same data store, allowing developers to leverage Core Data for unsupported calculations.
What's the best way to construct view state for populating non-optional attributes when adding a new model object?
- If a SwiftData model has non-optional properties, ensure they are set in the initializer for persistence.
- For views rendering a single object, fetch or find the data in a parent view. Only instantiate the subview if the object exists, otherwise display a placeholder.
- Alternatively, use a
QuerywithfetchLimitof one and handle the logic for zero or one object in the view. - A cleaner approach is to fetch the single model in a parent view using a
ModelContext. If it doesn't exist, create it before presenting the view. This avoids showing an empty list and handles creation logic outside the view.
How can SwiftData + CloudKit adopt an app group or schema version without losing data?
- Developers can add a version schema at any time.
- To move to an app group container, developers will likely need to copy data from the old container to the new one, as the group container resides in a different directory.
- If using the default model configuration, SwiftData will move the data for developers when moving to an app group. When a URL was specified previously, move the data manually to move it manually.
- Start version schemas with the current version and progressively build them up. Developers can add previous versions later to pick up those stores during migration.
How does using an app group for the SwiftData local store affect CloudKit development and production environments?
- Ensure the CloudKit entitlement used is appropriate for all apps reading from the app group, as they will be syncing to the same CloudKit container.
- If do not want data in the app group to sync to CloudKit, use a separate model configuration and data store for syncing.
- Avoid giving widgets or extensions the CloudKit entitlement, as this would require them to sync and can impact performance due to their limited runtime.
- Widget extensions have different system policies for network usage and syncing compared to full apps, often throttling network activity to conserve battery.
When evolving the schema in Xcode during development, do I need to do anything to avoid creating duplicates in the local store between two apps writing to the same CloudKit?
- Ensure schemas are aligned across all apps to prevent competing migrations.
- During development, flip environments to production on all apps to test schema synchronization in a production-like setting.
- Syncing is complex; use logs and sysdiagnose for debugging.
- Be mindful of autosave behavior; ensure saves are committed, especially when force quitting apps. Backgrounding apps in the simulator can simulate customer behavior.
What's the most performant way to count SwiftData objects without querying them in views?
- Use
fetchCounton theModelContext. - In performance-sensitive areas, manually fetching into a cached array can offer more control than
@Queryif SwiftUI's view lifecycle triggers excessive refetches. - Use Instruments (SwiftUI and Persistence) to understand why SwiftUI re-initializes views and triggers fetches.
- Breaking down views into smaller components with fewer dependencies can reduce unnecessary query executions.
ResultsObservercan provide an observable experience for managing manual fetches.HistoryObservercan be used to determine when manual fetches are necessary based on specific property changes.fetchIdentifierallows fetching only object identifiers without fetching the full model.- For queries outside the view hierarchy (e.g., background actors), a direct
fetchis appropriate instead of@Query. - The
sectionBymodifier onResultsObserverandQuery(via key path) sections data directly without in-memory grouping.
How can CloudKit synchronization be sped up between devices?
- Test thoroughly and examine logs for verbose information routed through Core Data.
- Be aware of platform-specific system policies that may throttle network usage (e.g., watches on cellular, devices under heavy load/heat).
- Backgrounding and foregrounding apps can sometimes encourage more aggressive syncing during testing.
- Check entitlements carefully, as incorrect or mismatched entitlements between debug and release configurations can cause sync issues.
- Use
sysdiagnosefrom all devices and file feedback reports for detailed analysis of sync problems. autosavecan batch changes, but ensure saves are committed, especially when testing rapidly. Force quitting can prevent autosave.- Backgrounding apps in the simulator can mimic customer sync behavior.
What's the best way to store enums, including those that conform to Codable with associated values, in SwiftData?
- If enum values are
Codable, SwiftData can persist them. - New in recent releases:
ResultsObserverandQuerysupport predicates with raw representable enumerations. - For enums with associated values that are difficult to encode, use the
.codableflag with theattributemacro. This leverages existingCodableconformance for persistence. - If developers need to query or sort by enum values, consider modeling them as SwiftData models with relationships rather than relying solely on
Codable. This allows for full predicate and sorting capabilities. - For types that are not directly owned or modifiable (e.g.,
MapKittypes,Measurement), usingCodableis a practical approach.
What are the best practices for using multiple model contexts? When does it make sense to split work across separate contexts?
- The decision depends on the objective. For background work without UI implications, split tasks to keep them coherent within a transaction.
- Model contexts represent a working set; changes are saved in a single transaction. Minimize cross-coordination between contexts to reduce friction and serialization overhead.
- Split work to allow for independent execution, minimizing the need for contexts to communicate or coordinate.
- On most platforms (phones, iPads, watches), avoid excessive concurrency. The NAND system is often the primary bottleneck.
- Start by measuring performance and benchmarking.
- SQLite databases support multiple readers concurrently with a single writer.
- Be cautious of diminishing returns with increased concurrency; it can lead to higher memory usage and I/O, and synchronization costs can outweigh benefits.
- Short-lived model contexts help keep memory footprints small by processing and saving data incrementally. Avoid hydrating large object graphs into memory.
How do I know if I need to paginate my queries instead of fetching all records of a given type?
- Use Instruments (Hitches, Persistence) to identify UI hitches and excessive object loading.
- Set a
fetchLimiton theQuerywhen developers only need a subset of data for a view. - Lazy stacks with an
onAppearmodifier on a progress spinner can trigger fetching more data as the user scrolls. - Restricting fetches to what's needed for the current view prevents performance degradation and the need for manual pagination.
Is it a good idea to combine SwiftData with a cloud backend for sync, or is that an anti-pattern?
- It's not an anti-pattern. SwiftData can serve as a local cache for the cloud backend data.
- Developers can implement a custom data store (e.g., JSON store) to work with the existing storage mechanism.
- Leverage
HistoryObserverto identify changes and sync them back to the cloud backend. - Using SwiftData for the local cache reduces the need to reimplement persistence logic.
- Custom data stores allow storing data in any format and adhering to specific security models.
- Developers can start with a JSON store and gradually migrate to a SQLite store as the app evolves.
What's the proper way to know if a database migration is required or not?
- If a widget attempts to load an unversioned schema, it will error out, indicating a migration is needed.
- Use user defaults to store the migrated version number. Widgets can then use this version to open the persistent container.
- Widgets and extensions should not perform migrations due to limited runtime resources.
- For the main app, developers can estimate migration progress by calculating the number of stages and overriding the
didMigratehandler for custom migration stages. - Consider the user experience during migration; provide informative updates rather than just a loading spinner.
What's the best way to do grouped queries, like grouping results by one property of a model and then displaying the results in sections?
- The
ResultsObserverandQueryAPIs now support sectioning by providing a key path to a persistent property. - This allows data to be sectioned directly, eliminating the need for in-memory grouping.
- The
SampleTripsapp demonstrates this capability, showing how to build UIs with grouped data.
What's the recommended way to handle concurrency when a model actor needs to return model data?
- Model objects themselves are not
Sendabledue to their reference-based nature and graph interactions within theModelContext. - Extract the necessary data (e.g., persistent identifier, structs, dictionaries) to pass to other actors.
- Avoid passing relationships or entire object graphs across actor boundaries, as this can lead to incomplete hydration or unexpected behavior.
- Be mindful of the strict
Sendableboundary and the cost of communication across concurrency domains.