Hello,
I recently published an app that uses Swift Data as its primary data storage. The app uses concurrency, background threads, async await, and BLE communication.
Sadly, I see my app incurs many fringe crashes, involving EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, EXC_BREAKPOINT, etc.
I followed these guidelines:
One ModelContainer that is stored as a global variable and used throughout.
ModelContexts are created separately for each task, changes are saved manually, and models are not passed around.
Threads with different ModelContexts might manipulate and/or read the same data simultaneously.
I was under the impression this meets the usage requirements.
I suspect perhaps the issue lies in my usage of contexts in a single await function, that might be paused and resumed on a different thread (although same execution path). Is that the case? If so, how should SwiftData be used in async scopes?
Is there anything else particularly wrong in my approach?
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
About 4 months ago, I shipped the first version of my app with 4 versioned schemas that, unintentionally, had the same versionIdentifier of 1.2.0 in 2 of them:
V1: 1.0.0
V2: 1.1.0
V3: 1.2.0
V4: 1.2.0
They are ordered correctly in the MigrationPlan, and they are all lightweight.
Migration works, SwiftData doesn't crash on init and I haven't encountered any issues related to this. The app syncs with iCloud.
Questions, preferable for anybody with knowledge of SwiftData internals:
What will break in SwiftData when there are 2 duplicate numbers?
Not that I would expect it to be safe, but does it happen to be safe to ship an update that changes V4's version to 1.3.0, what was originally intended?
Topic:
App & System Services
SubTopic:
iCloud & Data
Hi all,
In my SwiftUI / SwiftData / Cloudkit app which is a series of lists, I have a model object called Project which contains an array of model objects called subprojects:
final class Project1
{
var name: String = ""
@Relationship(deleteRule: .cascade, inverse: \Subproject.project) var subprojects : [Subproject]?
init(name: String)
{
self.name = name
self.subprojects = []
}
}
The user will select a project from a list, which will generate a list of subprojects in another list, and if they select a subproject, it will generate a list categories and if the user selects a category it will generate another list of child objects owned by category and on and on.
This is the pattern in my app, I'm constantly passing arrays of model objects that are the children of other model objects throughout the program, and I need the user to be able to add and remove things from them.
My initial approach was to pass these arrays as bindings so that I'd be able to mutate them. This worked for the most part but there were two problems: it was a lot of custom binding code and when I had to unwrap these bindings using init?(_ base: Binding<Value?>), my program would crash if one of these arrays became nil (it's some weird quirk of that init that I don't understand at al).
As I'm still learning the framework, I had not realized that the @model macro had automatically made my model objects observable, so I decided to remove the bindings and simply pass the arrays by reference, and while it seems these references will carry the most up to date version of the array, you cannot mutate them unless you have access to the parent and mutate it like such:
project.subcategories?.removeAll { $0 == subcategory }
project.subcategories?.append(subcategory)
This is weirding me out because you can't unwrap subcategories before you try to mutate the array, it has to be done like above. In my code, I like to unwrap all optionals at the moment that I need the values stored in them and if not, I like to post an error to the user. Isn't that the point of optionals? So I don't understand why it's like this and ultimately am wondering if I'm using the correct design pattern for what I'm trying to accomplish or if I'm missing something? Any input would be much appreciated!
Also, I do have a small MRE project if the explanation above wasn't clear enough, but I was unable to paste in here (too long), attach the zip or paste a link to Google Drive. Open to sharing it if anyone can tell me the best way to do so. Thanks!
Hello,
I tried to validate if my app was properly syncing to the cloud. To test this, I created some data in the app, and then deleted the app, and reinstalled. I was expecting the data to still exist but it isn't. Is this a valid test or is the data expected to be deleted when app is deleted?
Topic:
App & System Services
SubTopic:
iCloud & Data
Hello,
I am building a pretty large database (~40MB) to be used in my SwiftData iOS app as read-only.
While inserting and updating the data, I noticed a substantial increase in size (+ ~10MB).
A little digging pointed to ACHANGE and ATRANSACTION tables that apparently are dealing with Persistent History Tracking.
While I do appreciate the benefits of that, I prefer to save space.
Could you please point me in the right direction?
Hello 👋,
I encounter the "This model instance was invalidated because its backing data could no longer be found the store" crash with SwiftData. Which from what I understood means I try to access a model after it has been removed from the store (makes sense).
I made a quick sample to reproduce/better understand because there some case(s) I can't figure it out.
Let's take a concrete example, we have Home model and a Home can have many Room(s).
// Sample code
@MainActor
let foo = Foo() // A single reference
let database = Database(modelContainer: sharedModelContainer) // A single reference
@MainActor
class Foo {
// Properties to explicilty keep reference of model(s) for the purpose of the POC
var _homes = [Home]()
var _rooms = [Room]()
func fetch() async {
let homes = await database.fetch().map {
sharedModelContainer.mainContext.model(for: $0) as! Home
}
print(ObjectIdentifier(homes[0]), homes[0].rooms?.map(\.id)) // This will crash here or not.
}
// Same version of a delete function with subtle changes.
// Depending on the one you use calling delete then fetch will result in a crash or not.
// Keep a reference to only homes == NO CRASH
func deleteV1() async {
self._homes = await database.fetch().map {
sharedModelContainer.mainContext.model(for: $0) as! Home
}
await database.delete()
}
// Keep a reference to only rooms == NO CRASH
func deleteV2() async {
self._rooms = await database.fetch().map {
sharedModelContainer.mainContext.model(for: $0) as! Home
}[0].rooms ?? []
await database.delete()
}
// Keep a reference to homes & rooms == CRASH 💥
func deleteV3() async {
self._homes = await database.fetch().map {
sharedModelContainer.mainContext.model(for: $0) as! Home
}
self._rooms = _homes[0].rooms ?? []
// or even only retain reference to rooms that have NOT been deleted 🤔 like here "id: 2" make it crash
// self._rooms = _homes[0].rooms?.filter { r in r.id == "2" } ?? []
await database.delete()
}
}
Calling deleteV() then fetch() will result in a crash or not depending on the scenario.
I guess I understand deleteV1, deleteV2. In those case an unsaved model is served by the model(for:) API and accessing properties later on will resolve correctly. The doc says: "The identified persistent model, if known to the context; otherwise, an unsaved model with its persistentModelID property set to persistentModelID."
But I'm not sure about deleteV3. It seems the ModelContext is kind of "aware" there is still cyclic reference between my models that are retained in my code so it will serve these instances instead when calling model(for:) API ? I see my home still have 4 rooms (instead of 2). So I then try to access rooms that are deleted and it crash. Why of that ? I mean why not returning home with two room like in deleteV1 ?
Because SwiftData heavily rely on CoreData may be I miss a very simple thing here. If someone read this and have a clue for me I would be extremely graceful.
PS:
If someone wants to run it on his machine here's some helpful code:
// Database
let sharedModelContainer: ModelContainer = {
let schema = Schema([
Home.self,
Room.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
debugPrint(modelConfiguration.url.absoluteString.replacing("%20", with: "\\ "))
return try! ModelContainer(for: schema, configurations: [modelConfiguration])
}()
extension Database {
static let shared = Database(modelContainer: sharedModelContainer)
}
@ModelActor
actor Database {
func insert() async {
let r1 = Room(id: "1", name: "R1")
let r2 = Room(id: "2", name: "R2")
let r3 = Room(id: "3", name: "R3")
let r4 = Room(id: "4", name: "R4")
let home = Home(id: "1", name: "My Home")
home.rooms = [r1, r2, r3, r4]
modelContext.insert(home)
try! modelContext.save()
}
func fetch() async -> [PersistentIdentifier] {
try! modelContext.fetchIdentifiers(FetchDescriptor<Home>())
}
@MainActor
func delete() async {
let mainContext = sharedModelContainer.mainContext
try! mainContext.delete(
model: Room.self,
where: #Predicate { r in
r.id == "1" || r.id == "4"
}
)
try! mainContext.save()
// 🤔 Calling fetch here seems to solve crash too, force home relationship to be rebuild correctly ?
// let _ = try! sharedModelContainer.mainContext.fetch(FetchDescriptor<Home>())
}
}
// Models
@Model
class Home: Identifiable {
@Attribute(.unique) public var id: String
var name: String
@Relationship(deleteRule: .cascade, inverse: \Room.home)
var rooms: [Room]?
init(id: String, name: String, rooms: [Room]? = nil) {
self.id = id
self.name = name
self.rooms = rooms
}
}
@Model
class Room: Identifiable {
@Attribute(.unique) public var id: String
var name: String
var home: Home?
init(id: String, name: String, home: Home? = nil) {
self.id = id
self.name = name
self.home = home
}
}
For a CRM application, I want users to be able to switch between accounts and have their saved contacts stored locally. Whenever a user logs in, the app should fetch data from their specific database location.
What’s the best practice to achieve this?
Should I create a separate database for each user?
Should I store all the data in one database and filter it by user?
Or is there a better approach I should consider?
If an app is using top-level models, meaning they exist outside the VersionedSchema enum, is it safe to keep them outside of the VersionedSchema enum and use a migration plan for simple migrations. Moving the models within the VersionedSchema enum I believe would change the identity of the models and result in data being lost, although correct me if I'm wrong in that statement.
The need presently is just to add another variable to the model and then set that variable within the init function:
var updateId = UUID()
The app is presently in TestFlight although I'd like to preserve data for users that are currently using the app.
The data within SwiftData is synchronized with CloudKit and so I'd also like to avoid any impact to synchronization.
Any thoughts on this would be greatly appreciated.
Definitely one of the stranger quirks of SwiftData I've come across.
I have a ScriptView that shows Line entities related to a Production, and a TextEnterScriptView that’s presented in a sheet to input text.
I’m noticing that every time I type in the TextEditor within TextEnterScriptView, a new Line shows up in ScriptView — even though I haven’t explicitly inserted it into the modelContext.
I'm quite confused because even though I’m only assigning a new Line to a local @State array in TextEnterScriptView, every keystroke in the TextEditor causes a duplicate Line to appear in ScriptView.
In other words, Why is SwiftData creating new Line entities every time I type in the TextEditor, even though I’m only assigning to a local @State array and not explicitly inserting them into the modelContext?
Here is my minimal reproducible example:
import SwiftData
import SwiftUI
@main
struct testApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(for: Line.self, isAutosaveEnabled: false)
}
}
}
struct ContentView: View {
@Environment(\.modelContext) var modelContext
@Query(sort: \Production.title) var productions: [Production]
var body: some View {
NavigationStack {
List(productions) { production in
NavigationLink(value: production) {
Text(production.title)
}
}
.navigationDestination(for: Production.self) { production in
ScriptView(production: production)
}
.toolbar {
Button("Add", systemImage: "plus") {
let production = Production(title: "Test \(productions.count + 1)")
modelContext.insert(production)
do {
try modelContext.save()
} catch {
print(error)
}
}
}
.navigationTitle("Productions")
}
}
}
struct ScriptView: View {
@Query private var lines: [Line]
let production: Production
@State private var isShowingSheet: Bool = false
var body: some View {
List {
ForEach(lines) { line in
Text(line.content)
}
}
.toolbar {
Button("Show Sheet") {
isShowingSheet.toggle()
}
}
.sheet(isPresented: $isShowingSheet) {
TextEnterScriptView(production: production)
}
}
}
struct TextEnterScriptView: View {
@Environment(\.dismiss) var dismiss
@State private var text = ""
@State private var lines: [Line] = []
let production: Production
var body: some View {
NavigationStack {
TextEditor(text: $text)
.onChange(of: text, initial: false) {
lines = [Line(content: "test line", production: production)]
}
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}
@Model
class Production {
@Attribute(.unique) var title: String
@Relationship(deleteRule: .cascade, inverse: \Line.production)
var lines: [Line] = []
init(title: String) {
self.title = title
}
}
@Model
class Line {
var content: String
var production: Production?
init(content: String, production: Production?) {
self.content = content
self.production = production
}
}
If Cloudkit is enabled, SwiftData @Query operation hangs when the View scenePhase becomes active.
Seems like the more @Query calls you have, the more it hangs.
This has been first documented some time ago, but in typical Apple style, it has not been addressed or even commented on.
https://developer.apple.com/forums/thread/761434
Hello,
I'm trying to work on an iPadOS and macOS app that will rely on the document-based system to create some kind of orientation task to follow.
Let say task1.myfile will be a check point regulation from NYC to SF and task2.myfile will be a visit as many key location as you can in SF.
The file represent the specific landmark location and rules of the game.
And once open, I will be able to read KML/GPS file to evaluate their score based with the current task.
But opened GPS files does not have to be stored in the task file itself, it stay alongside.
I wanted to use that scenario to experiment with SwiftData (I'm a long time CoreData user, I even wrote my own WebDAV based persistent store back in the day), and so, mix both on file and in memory persistent store, with distribution based on object class.
With CoreData it would have been possible, but I do not see how to achieve that with SwiftData and DocumentGroup integration.
Any idea how to do that?
I'm experiencing a persistent issue with CloudKit sharing in my iOS application. When attempting to present a UICloudSharingController, I receive the error message "Unknown client: ChoreOrganizer" in the console.
App Configuration Details:
App Name: ChoreOrganizer
Bundle ID: com.ProgressByBits.ChoreOrganizer
CloudKit Container ID: iCloud.com.ProgressByBits.ChoreOrganizer
Core Data Model Name: ChoreOrganizer.xcdatamodeld
Core Data Entity: Chore
Error Details:
The error "Unknown client: ChoreOrganizer" occurs when I present the UICloudSharingController
This happens only on the first attempt to share; subsequent attempts during the same app session don't show the error but sharing still doesn't work
All my code executes successfully without errors until UICloudSharingController is presented
Implementation Details:
I'm using NSPersistentCloudKitContainer for Core Data synchronization and UICloudSharingController for sharing. My implementation creates a custom CloudKit zone, saves both a record and a CKShare in that zone, and then presents the sharing controller.
Here's the relevant code:
@MainActor
func presentSharing(from viewController: UIViewController) async throws {
// Create CloudKit container
let container = CKContainer(identifier: containerIdentifier)
let database = container.privateCloudDatabase
// Define custom zone ID
let zoneID = CKRecordZone.ID(zoneName: "SharedChores", ownerName: CKCurrentUserDefaultName)
do {
// Check if zone exists, create if necessary
do {
_ = try await database.recordZone(for: zoneID)
} catch {
let newZone = CKRecordZone(zoneID: zoneID)
_ = try await database.save(newZone)
}
// Create record in custom zone
let recordID = CKRecord.ID(recordName: "SharedChoresRoot", zoneID: zoneID)
let rootRecord = CKRecord(recordType: "ChoreRoot", recordID: recordID)
rootRecord["name"] = "Shared Chores Root" as CKRecordValue
// Create share
let share = CKShare(rootRecord: rootRecord)
share[CKShare.SystemFieldKey.title] = "Shared Tasks" as CKRecordValue
// Save both record and share in same operation
let recordsToSave: [CKRecord] = [rootRecord, share]
_ = try await database.modifyRecords(saving: recordsToSave, deleting: [])
// Present sharing controller
let sharingController = UICloudSharingController(share: share, container: container)
sharingController.delegate = shareDelegate
// Configure popover
if let popover = sharingController.popoverPresentationController {
popover.sourceView = viewController.view
popover.sourceRect = CGRect(
x: viewController.view.bounds.midX,
y: viewController.view.bounds.midY,
width: 1, height: 1
)
popover.permittedArrowDirections = []
}
viewController.present(sharingController, animated: true)
} catch {
throw error
}
}
Steps I've already tried:
Verified correct bundle ID and container ID match in all places (code, entitlements file, Developer Portal)
Added NSUbiquitousContainers configuration to Info.plist
Ensured proper entitlements in the app
Created and configured proper provisioning profiles
Tried both default zone and custom zone for sharing
Various ways of saving the record and share (separate operations, same operation)
Cleaned build folder, deleted derived data, reinstalled the app
Tried on both simulator and physical device
Confirmed CloudKit container exists in CloudKit Dashboard with correct schema
Verified iCloud is properly signed in on test devices
Console Output:
1. Starting sharing process
2. Created CKContainer with ID: iCloud.com.ProgressByBits.ChoreOrganizer
3. Using zone: SharedChores
4. Checking if zone exists
5. Zone exists
7. Created record with ID: <CKRecordID: 0x3033ebd80; recordName=SharedChoresRoot, zoneID=SharedChores:__defaultOwner__>
8. Created share with ID: <CKRecordID: 0x3033ea920; recordName=Share-C4701F43-7591-4436-BBF4-6FA8AF3DF532, zoneID=SharedChores:__defaultOwner__>
9. About to save record and share
10. Records saved successfully
11. Creating UICloudSharingController
12. About to present UICloudSharingController
13. UICloudSharingController presented
Unknown client: ChoreOrganizer
Additional Information:
When accessing the CloudKit Dashboard, I can see that data is being properly synced to the cloud, indicating that the basic CloudKit integration is working. The issue appears to be specific to the sharing functionality.
I would greatly appreciate any insights or solutions to resolve this persistent "Unknown client" error. Thank you for your assistance.
The stuff I've found by searching has confused me, so hopefully someone can help simplify it for me?
I have an app (I use it for logging which books I've given away), and I could either add a bunch of things to the app, or I could have another app (possibly a CLI tool) to generate some reports I'd like.
After copying and inserting instances I am getting strange duplicate values in arrays before saving.
My models:
@Model
class Car: Identifiable {
@Attribute(.unique)
var name: String
var carData: CarData
func copy() -> Car {
Car(
name: "temporaryNewName",
carData: carData
)
}
}
@Model
class CarData: Identifiable {
var id: UUID = UUID()
var featuresA: [Feature]
var featuresB: [Feature]
func copy() -> CarData {
CarData(
id: UUID(),
featuresA: featuresA,
featuresB: featuresB
)
}
}
@Model
class Feature: Identifiable {
@Attribute(.unique)
var id: Int
@Attribute(.unique)
var name: String
@Relationship(
deleteRule:.cascade,
inverse: \CarData.featuresA
)
private(set) var carDatasA: [CarData]?
@Relationship(
deleteRule:.cascade,
inverse: \CarData.featuresB
)
private(set) var carDatasB: [CarData]?
}
The Car instances are created and saved to SwiftData, after that in code:
var fetchDescriptor = FetchDescriptor<Car>(
predicate: #Predicate<Car> {
car in
car.name == name
}
)
let cars = try! modelContext.fetch(
fetchDescriptor
)
let car = cars.first!
print("car featuresA:", car.featuresA.map{$0.name}) //prints ["green"] - expected
let newCar = car.copy()
newCar.name = "Another car"
newcar.carData = car.carData.copy()
print("newCar featuresA:", newCar.featuresA.map{$0.name}) //prints ["green"] - expected
modelContext.insert(newCar)
print("newCar featuresA:", newCar.featuresA.map{$0.name}) //prints ["green", "green"] - UNEXPECTED!
/*some code planned here modifying newCar.featuresA, but they are wrong here causing issues,
for example finding first expected green value and removing it will still keep the unexpected duplicate
(unless iterating over all arrays to delete all unexpected duplicates - not optimal and sloooooow).*/
try! modelContext.save()
print("newCar featuresA:", newCar.featuresA.map{$0.name}) //prints ["green"] - self-auto-healed???
Tested on iOS 18.2 simulator and iOS 18.3.1 device. Minimum deployment target: iOS 17.4
The business logic is that new instances need to be created by copying and modifying previously created ones, but I would like to avoid saving before all instances are created, because saving after creating each instance separately takes too much time overall. (In real life scenario there are more than 10K objects with much more properties, updating just ~10 instances with saving takes around 1 minute on iPhone 16 Pro.)
Is this a bug, or how can I modify the code (without workarounds like deleting duplicate values) to not get duplicate values between insert() and save()?
We are currently implementing a custom iCloud sync for our macOS and iOS apps using CloudKit. Syncing works fine as long as the number of record sends is relatively small.
But when we test with a large number of changes ( 80,000+ CKRecords ) we start running into problems.
Our sending strategy is very conservative to avoid rate limits:
We send records sequentially in batches of 250 records
With about 2 seconds pause between operations
Records are small and contain no assets (assets are uploaded separately)
At some point we start receiving:
“Database commit size exceeds limit”
After that, CloudKit begins returning rate-limit errors with retryAfter-Information in the error.
We wait for the retry time and try again, but from this moment on, nothing progresses anymore. Every subsequent attempt fails.
We could not find anything in the official documentation regarding such a “commit size” limit or what triggers this failure state.
So my questions are:
Are there undocumented limits on the total number of records that can exist in an iCloud database (private or shared)?
Is there a maximum volume of record modifications a container can accept within a certain timeframe, even if operations are split into small batches with pauses?
Is it possible that sending large numbers of records in a row can temporarily or permanently “stall” a CloudKit container?
Any insights or experiences would be greatly appreciated.
Thank you!
Hi all,
I recently discovered that I forgot to deploy my CloudKit schema changes from development to production - an oversight that unfortunately went unnoticed for 2.5 months.
As a result, any data created during that time was never synced to iCloud and remains only in the local CoreData store. Once I pushed the schema to production, CloudKit resumed syncing new changes as expected.
However, this leaves me with a gap: there's now a significant amount of data that would be lost if users delete or reinstall the app.
Before I attempt to implement a manual backup or migration strategy, I was wondering:
Does NSPersistentCloudKitContainer keep track of local changes that couldn't be synced doe to the missing schema and automatically reattempt syncing them now that the schema is live?
If not, what would be the best approach to ensure this "orphaned" data gets saved to CloudKit retroactively.
Thanks in advance for any guidance or suggestions.
I am trying to add a custom JSON DataStore and DataStoreConfiguration for SwiftData. Apple kindly provided some sample code in the WWDC24 session, "Create a custom data store with SwiftData", and (once updated for API changes since WWDC) that works fine.
However, when I try to add a relationship between two classes, it fails. Has anyone successfully made a JSONDataStore with a relationship?
Here's my code; firstly the cleaned up code from the WWDC session:
import SwiftData
final class JSONStoreConfiguration: DataStoreConfiguration {
typealias Store = JSONStore
var name: String
var schema: Schema?
var fileURL: URL
init(name: String, schema: Schema? = nil, fileURL: URL) {
self.name = name
self.schema = schema
self.fileURL = fileURL
}
static func == (lhs: JSONStoreConfiguration, rhs: JSONStoreConfiguration) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
final class JSONStore: DataStore {
typealias Configuration = JSONStoreConfiguration
typealias Snapshot = DefaultSnapshot
var configuration: JSONStoreConfiguration
var name: String
var schema: Schema
var identifier: String
init(_ configuration: JSONStoreConfiguration, migrationPlan: (any SchemaMigrationPlan.Type)?) throws {
self.configuration = configuration
self.name = configuration.name
self.schema = configuration.schema!
self.identifier = configuration.fileURL.lastPathComponent
}
func save(_ request: DataStoreSaveChangesRequest<DefaultSnapshot>) throws -> DataStoreSaveChangesResult<DefaultSnapshot> {
var remappedIdentifiers = [PersistentIdentifier: PersistentIdentifier]()
var serializedData = try read()
for snapshot in request.inserted {
let permanentIdentifier = try PersistentIdentifier.identifier(for: identifier,
entityName: snapshot.persistentIdentifier.entityName,
primaryKey: UUID())
let permanentSnapshot = snapshot.copy(persistentIdentifier: permanentIdentifier)
serializedData[permanentIdentifier] = permanentSnapshot
remappedIdentifiers[snapshot.persistentIdentifier] = permanentIdentifier
}
for snapshot in request.updated {
serializedData[snapshot.persistentIdentifier] = snapshot
}
for snapshot in request.deleted {
serializedData[snapshot.persistentIdentifier] = nil
}
try write(serializedData)
return DataStoreSaveChangesResult<DefaultSnapshot>(for: self.identifier, remappedIdentifiers: remappedIdentifiers)
}
func fetch<T>(_ request: DataStoreFetchRequest<T>) throws -> DataStoreFetchResult<T, DefaultSnapshot> where T : PersistentModel {
if request.descriptor.predicate != nil {
throw DataStoreError.preferInMemoryFilter
} else if request.descriptor.sortBy.count > 0 {
throw DataStoreError.preferInMemorySort
}
let objs = try read()
let snapshots = objs.values.map({ $0 })
return DataStoreFetchResult(descriptor: request.descriptor, fetchedSnapshots: snapshots, relatedSnapshots: objs)
}
func read() throws -> [PersistentIdentifier : DefaultSnapshot] {
if FileManager.default.fileExists(atPath: configuration.fileURL.path(percentEncoded: false)) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let data = try decoder.decode([DefaultSnapshot].self, from: try Data(contentsOf: configuration.fileURL))
var result = [PersistentIdentifier: DefaultSnapshot]()
data.forEach { s in
result[s.persistentIdentifier] = s
}
return result
} else {
return [:]
}
}
func write(_ data: [PersistentIdentifier : DefaultSnapshot]) throws {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let jsonData = try encoder.encode(data.values.map({ $0 }))
try jsonData.write(to: configuration.fileURL)
}
}
The data model classes:
import SwiftData
@Model
class Settings {
private(set) var version = 1
@Relationship(deleteRule: .cascade) var hack: Hack? = Hack()
init() {
}
}
@Model
class Hack {
var foo = "Foo"
var bar = 42
init() {
}
}
Container:
lazy var mainContainer: ModelContainer = {
do {
let url = // URL to file
let configuration = JSONStoreConfiguration(name: "Settings", schema: Schema([Settings.self, Hack.self]), fileURL: url)
return try ModelContainer(for: Settings.self, Hack.self, configurations: configuration)
}
catch {
fatalError("Container error: \(error.localizedDescription)")
}
}()
Load function, that saves a new Settings JSON file if there isn't an existing one:
@MainActor func loadSettings() {
let mainContext = mainContainer.mainContext
let descriptor = FetchDescriptor<Settings>()
let settingsArray = try? mainContext.fetch(descriptor)
print("\(settingsArray?.count ?? 0) settings found")
if let settingsArray, let settings = settingsArray.last {
print("Loaded")
} else {
let settings = Settings()
mainContext.insert(settings)
do {
try mainContext.save()
} catch {
print("Error saving settings: \(error)")
}
}
}
The save operation creates a JSON file, which while it isn't a format I would choose, is acceptable, though I notice that the "hack" property (the relationship) doesn't have the correct identifier.
When I run the app again to load the data, I get an error (that there wasn't room to include in this post).
Even if I change Apple's code to not assign a new identifier, so the relationship property and its pointee have the same identifier, it still doesn't load.
Am I doing something obviously wrong, or are relationships not supported in custom data stores?
Hi. I'm hoping someone might be able to help us with an issue that's been affecting our standalone watchOS app for some time now.
We've encountered consistent crashes on Apple Watch devices when the app enters the background while the device is offline (i.e., no Bluetooth and no Wi-Fi connection). Through extensive testing, we've isolated the problem to the use of NSPersistentCloudKitContainer. When we switch to NSPersistentContainer, the crashes no longer occur.
Interestingly, this issue only affects our watchOS app. The same CloudKit-based persistence setup works reliably on our iOS and macOS apps, even when offline. This leads us to believe the issue may be specific to how NSPersistentCloudKitContainer behaves on watchOS when the device is disconnected from the network.
We're targeting watchOS 10 and above. We're unsure if this is a misconfiguration on our end or a potential system-level issue, and we would greatly appreciate any insight or guidance.
Greetings i have an app that uses three different SwiftData models and i want to know what is the best way to use the them accross the app. I though a centralized behaviour and i want to know if it a correct approach.First let's suppose that the first view of the app will load the three models using the @Enviroment that work with @Observation. Then to other views that add data to the swiftModels again with the @Environment. Another View that will use the swiftData models with graph and datas for average and min and max.Is this a corrent way? or i should use @Query in every view that i want and ModelContext when i add the data.
@Observable
class CentralizedDataModels {
var firstDataModel: [FirstDataModel] = []
var secondDataModel: [SecondDataModel] = []
var thirdDataModel: [ThirdDataModel] = []
let context: ModelContext
init(context:ModelContext) {
self.context = context
}
}
Environment
visionOS 26
Xcode 26
Issue
I am experiencing crash when trying to access a [String] from a @Model data, after dismissing an immersiveSpace and opening a WindowGroup.
This crash only occurs when trying to access the [String] property of my Model. It works fine with other properties.
Thread 1: Fatal error: This backing data was detached from a context without resolving attribute faults: PersistentIdentifier(...)
Steps to Reproduce
Open WindowGroup
Dismiss window, open ImmersiveSpace
Dismiss ImmersiveSpace, reopen WindowGroup
Any guidance would be appreciated!
@main
struct MyApp: App {
var body: some Scene {
WindowGroup(id: "main") {
ContentView()
}
.modelContainer(for: [Item.self])
ImmersiveSpace(id: "immersive") {
ImmersiveView()
}
}
}
// In SwiftData model
@Model
class Item {
var title: String = "" // Accessing this property works fine
var tags: [String] = []
@storageRestrictions(accesses: _$backingData, initializes: _tags)
init(initialValue) {
_$backingData.setValue(forKey: \. tags, to: initialValue)
_tags =_ SwiftDataNoType()
}
get {
_$observationRegistrar.access(self, keyPath: \.tags)
**return self getValue(forkey: \.tags)** // Crashes here
}