I just wanted to start out by saying that this is an abridged version of my original published version from December 22, 2023 that you can find on my website here.
Part of the back end code of one of my apps is composed of asynchronous functions that I call and then await their returned results. When moving my apps over from CoreData
to SwiftData
the data context that used to be passed into each function was dropped and I noticed it didn't act consistently anymore. The main problems were:
EXC_BAD_ACCESS
The first error I had was a generalized EXC_BAD_ACCESS (code=1, address=...)
that showed up mainly in my entry App @main struct but also popped up at various entity relationships spots all under various thread count numbers.
Fatal Error: Duplicate Keys
At some point in all of this when I couldn't figure out how to fix the issue I decided to at least help mitigate it by manually saving the data context after each related entity was done being created. I figured this way if the program crashed at least the data that had been created before that point would be saved and the user wouldn't have to start back at the beginning each time. This didn't help as once I added saving, at any of the points, I got fatal errors on my entities complaining about duplicate keys.
Fatal error: Duplicate keys of type 'EntityName' were found in a Dictionary.
This usually means either that the type violates Hashable's requirements, or that members of such a dictionary were mutated after insertion.
The Solution
To solve this I realized I needed to stop passing in the main data entity and instead pass in it's PersistentIdentifier
along with it's ModelContext
so it can be recreated in the asynchronous and/or problematic functions. This worked!
So, for example, if I used pass in the entity directly to a function like:
private func doSomething(entity: EntityName, ...) async {
// ... doing whatever happened to the entity
}
I'd now call the new method with doSomething(in: entity.modelContext!.container, entityID: entity.persistentModelID, ...)
instead and the function itself would look like:
private func doSomething(in container: ModelContainer, entityID: PersistentIdentifier, ...) async {
// First recreate the main entity in it's own ModelContext
let modelContext = ModelContext(container)
modelContext.autosaveEnabled = false
let entity = modelContext.model(for: entityID) as? EntityName
if entity == nil {
// Can't continue without entity
// TODO: Handle any error handling and...
return
}
// ... doing whatever happened to the entity
do {
try modelContext.save()
} catch {
// It can't be saved
// TODO: Handle any error handling and...
return
}
// Success!
}
With the different data context used in my problematic asynchronous methods both my fatal and bad access errors are gone!
If you want more information about how I solved this along with a quick stop in concurrency check out my original version published in December 22, 2023 on my main website.
Have a great day!
Top comments (0)