DEV Community

Cover image for SwiftData: Solving Fatal Errors and EXC_BAD_ACCESS While Handling Entities on Different Threads
Kyra
Kyra

Posted on • Updated on • Originally published at simplykyra.com

SwiftData: Solving Fatal Errors and EXC_BAD_ACCESS While Handling Entities on Different Threads

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.

Image shows the EXC_BAD_ACCESS error on a relationship

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.
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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!
}
Enter fullscreen mode Exit fullscreen mode

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)