SwiftData insert crashes with EXC_BAD_ACCESS using background thread from ModelActor – Swift

by
Ali Hasan
swift-concurrency swift-data

Quick Fix: Try referencing the model container and using its context directly instead of using the executor. Update the code to:

actor BackgroundActor: ModelActor {
let container: ModelContainer
init(container: ModelContainer)
}

func printDataLabels(biggerThan maxSize: Int) {
let context = self.container.context
// rest of the method }

The Problem:

With Xcode Version 15.0 beta 6 (15A5219j), an App using SwiftData for Entity operations on a background thread crashes with an ‘EXC_BAD_ACCESS’ error during inserts. The issue occurs inconsistently and switching ‘context.autosaveEnabled’ to ‘false’ modifies the intervals at which the crashes occur. The cause of the crashes is believed to lie within SwiftData’s internal pointer handling, specifically with ‘_$backingData’. Is there a reliable solution to perform background inserts with SwiftData?

The Solutions:

Solution 1: ModelActor on a different thread:

To insert data into a Core Data database using SwiftData in the background, you need to create the ModelActor on a different thread than the main thread. You can do this by instantiating the ModelActor within a Task and passing the ModelContainer as a parameter since the ModelContainer is Sendable. Here’s an example:

actor BackgroundActor: ModelActor {
    let executor: any ModelExecutor

    init(container: ModelContainer) {
        self.executor = DefaultModelExecutor(context: ModelContext(container))
        // you can optionally disable autosave here if desired
        // self.executor.context.autosaveEnabled = false
    }

    func doStuff() {
        for i in 0..<10000 {
            let newItem = Item(timestamp: .now)
            context.insert(newItem)
        }
        try? context.save()
    }
}

Task {
    await BackgroundActor(container: modelContext.container).doStuff()
}

In the above code, the BackgroundActor is created within a Task, and the ModelContainer is passed as a parameter. The doStuff() method is then called, which inserts 10000 new items into the database and saves the changes to disk.

Q&A

How to run background tasks with SwiftData on a separate thread in order to prevent crashes?

Create a ModelActor on a different thread using a Task and pass the ModelContainer as a parameter.

How to use the ModelContainer in a ModelActor?

Capture a reference to the ModelContainer in the ModelActor and use the context from the container.