Error accessing backing data on deleted item in detached task

I have been working on an app for the past few months, and one issue that I have encountered a few times is an error where quick subsequent deletions cause issues with detached tasks that are triggered from some user actions.

Inside a Task.detached, I am building an isolated model context, querying for LineItems, then iterating over those items. The crash happens when accessing a Transaction property through a relationship.

var byTransactionId: [UUID: [LineItem]] {
    return Dictionary(grouping: self) { item in
        item.transaction?.id ?? UUID()
    }
}

In this case, the transaction has been deleted, but the relationship existed when the fetch occurred, so the transaction value is non-nil. The crash occurs when accessing the id. This is the error.

SwiftData/BackingData.swift:1035: Fatal error: This model instance was invalidated because its backing data could no longer be found the store. PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(backing: SwiftData.PersistentIdentifier.PersistentIdentifierBacking.managedObjectID(0xb43fea2c4bc3b3f5 <x-coredata://A9EFB8E3-CB47-48B2-A7C4-6EEA25D27E2E/Transaction/p1756>)))

I see other posts about this error and am exploring some suggestions, but if anyone has any thoughts, they would be appreciated.

Yeah, this is an interesting topic. Consider the following flow:

  1. In a background task that runs for long time, you fetch a SwiftData model object and hold it for later use.
  2. In the main queue, the object is deleted via user interaction.
  3. In a background task, you access the object.

Based on the current implementation of SwiftData, step 3 will trigger an error because the object was deleted.

In the Core Data world, you can use query generations to guarantee the object being valid in step 3, as discussed in Accessing data when the store changes. In the case where, from a managed object context pinned to a generation, you change a piece of data that has been changed, saving the context triggers a conflict, which you can handle via NSMergePolicy.

SwiftData doesn't have query generations, and so I don't see an ideal pattern to handle the kind of issue. I may consider the following:

a. Avoid accessing a same piece of data simultaneously by shorten the time window. Concretely:

  • In the background task, avoid holding a SwiftData model object for long time. Instead, fetch the object when needed, use it, and forget it. This shortens the time window between fetching and using the object.

  • Use batch delete with a predicate or cascade delete via a relationship, rather than deleting objects one by one. This shortens the time window of the deletion.

  • Observe .didSave and .NSPersistentStoreRemoteChange, and from there, check the store history for relevant changes.

b. Implement your own mechanism to coordinate the data access, if your app does need to access the data randomly and concurrently. This includes identifying the models you need to access concurrently, and use your own mechanism to coordinate. Depending on your concrete use casen, this may be involved.

Given this situation, I’d suggest that you file a feedback report for SwiftData to support features like query generation and conflict resolution – If you do so, please share your report ID here.

I'm super interested in knowing if the above information helps, and what you eventually choose to do, if you don't mind to share.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Error accessing backing data on deleted item in detached task
 
 
Q