Ongoing Issues with ModelActor in SwiftData?

After the problems with the ModelActor in iOS 18, it seemed like the ModelActor became more stable with iOS 18.1 and macOS 15.1.

However, I’m still encountering many problems and crashes. I wanted to ask if these issues are related to my persistence layer architecture or if they’re still inherent to the ModelActor itself.

I’ve generally followed the blog posts:

https://fatbobman.com/en/posts/practical-swiftdata-building-swiftui-applications-with-modern-approaches/

and

https://brightdigit.com/tutorials/swiftdata-modelactor/

and aim to achieve the following:

I have a single DataProvider that holds the ModelContainer and uses it to configure and initialize a single DataHandler. These are created once at app launch and injected into the SwiftUI view hierarchy as EnvironmentObjects. Since I need to access the SwiftData models not only in SwiftUI but also indirectly in ViewModels or UIKit views, all read operations on the models should go through the DataProvider ( ModelContainrs MainContext), while all other CRUD operations are handled centrally via the single DataHandler (executed within a single ModelActor).

Additionally, I want to monitor the entire container using another ModelActor, initialized in the DataProvider, which tracks changes to objects using TransactionHistory.

I’ve managed to implement this to some extent, but I’m facing two main issues:

  1. ModelActor and Main Actor Requirement

The ModelActor only updates SwiftUI views when initialized via the main context of the ModelContainer and therefore runs on the Main Actor. It would be ideal for this to work in the background, but the issue with the ModelActor that existed previously doesn’t seem to have been resolved in iOS 18.1/macOS 15.1—am I wrong about this?

  1. Frequent Crashes (more severe)

Crashes occur, especially when multiple windows on macOS or iPadOS access the same DataHandler to update models. This often leads to crashes during read operations on models by a SwiftUI view, with logs like:

Object 0x111f15480 of class _ContiguousArrayStorage deallocated with non-zero retain count 3. This object's deinit, or something called from it, may have created a strong reference to self which outlived deinit, resulting in a dangling reference.

error: the replacement path doesn't exist: "/var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift"
Can't show file for stack frame : <DBGLLDBStackFrame: 0x34d28e170> - stackNumber:1 - name:Tag.uuID.getter. The file path does not exist on the file system: /var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift

This error usually happens when there are multiple concurrent accesses to the DataHandler/ModelActor. However, crashes also occur sporadically during frequent accesses from a single view with an error like "the replacement path doesn't exist."

It also seems like having multiple ModelActors, as in this case (one for observation and one for data changes), causes interference and instability. The app appears to crash less frequently when the observer is not initialized, but I can’t verify this—it might just be a coincidence.

My Question: Am I fundamentally doing something wrong with the ModelActors or the architecture of my persistence layer?

You said:

It also seems like having multiple ModelActors, as in this case (one for observation and one for data changes), causes interference and instability.

Yeah, having multiple ModelActors seems like an invitation for problems. I use a single ModelActor for all interaction with the data store and I don’t have any of these problems. And if doing anything very slow (like inserting thousands of records), just add an await Task.yield() in the loop and that will keep it responsive.


Note, there is a bug in ModelActor which runs it on the main queue (!), so I avoid the @ModelActor macro and just define my own actor:

actor FooModelActor {
    private let modelExecutor: any ModelExecutor
    private let modelContainer: ModelContainer
    private let modelContext: ModelContext

    init(inMemoryOnly: Bool = false) {
        let schema = Schema(…)
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: inMemoryOnly)
        modelContainer = try! ModelContainer(for: schema, configurations: [modelConfiguration])
        modelContext = ModelContext(modelContainer)
        modelExecutor = DefaultSerialModelExecutor(modelContext: modelContext)
    }

    …
}

Basically, I expanded the @ModelActor macro and grabbed the salient properties, but avoided the portions that introduced this main thread problem.

@eoonline: Unfortunately, the approach with a custom ModelActor doesn’t work when there is simultaneous access from multiple windows or when there are many accesses.

Does anyone else have experiences or suggestions?

ModelActor and Main Actor Requirement

My first suggestion will be that you avoid using ModelContainer.mainContext with ModelActor, and here is the reason:

  • ModelContainer.mainContext is already MainActor isolated, and so wrapping it with ModelActor doesn't seem reasonable to me.

  • The default serial model executor (DefaultSerialModelExecutor), which is used in the default implementation of ModelActor, changes the internal state of the model context passed to its init(modelContext:). Concretely, if you use ModelContainer.mainContext to create a DefaultSerialModelExecutor, SwiftData will unbind the UI state of mainContext, which may impact the use of mainContext in other places.

If the intent of using ModelContainer.mainContext and MainActor together is to modularize the code that accesses the main model context, you might consider packaging the code with a type and annotating it with @MainActor.

The ModelActor only updates SwiftUI views when initialized via the main context of the ModelContainer and therefore runs on the Main Actor. It would be ideal for this to work in the background, but the issue with the ModelActor that existed previously doesn’t seem to have been resolved in iOS 18.1/macOS 15.1—am I wrong about this?

There was a bug that changing a SwiftData model from a ModelActor didn't trigger a SwiftUI update, as discussed in Importing Data into SwiftData in the Background Using ModelActor and @Query. The situation is supposed to be improved, but if you still see something like that, I'd suggest that you file a feedback report for the SwiftData team to investigate – If you do so, please share your report ID here.

Frequent Crashes (more severe)

If you still see crashes after avoiding wrapping ModelContainer.mainContext with ModelActor, please provide a minimal project, with detailed steps, that reproduces the issue. I'd be interested in taking a closer look.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Ongoing Issues with ModelActor in SwiftData?
 
 
Q