swiftswiftuiasync-awaitswiftdata

Perform SwiftData operations with @ModelActor on a background thread


So I don't know how to perform SwiftData operations on a background thread. Every time I call my @ModelActor, everything runs on the main thread. I've read online that you must instantiate it on a background thread, but it's still executing methods on the main thread. I've also read about executors, but couldn't find anything working.

main thread

EDIT: It seems calling a MainActor in the .task { introduces the issue?

Here's my code so far:

import SwiftUI
import SwiftData

@Model
class User {
    var id: UUID
    
    init(id: UUID) {
        self.id = id
    }
}

@MainActor
final class SomeMainActor {}

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    do {
                        let savedUserProvider = try await SavedUserProvider.createOnBackground()
                        let someMainActor = SomeMainActor()
                        await savedUserProvider.updateUser()
                    } catch {}
                }
        }
    }
}

@ModelActor
actor SavedUserProvider {
    static func createOnBackground() async throws -> SavedUserProvider {
        await Task.detached(priority: .background) {
            let container: ModelContainer = {
                let schema = Schema([
                    User.self
                ])
                let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
                
                do {
                    return try ModelContainer(for: schema, configurations: [modelConfiguration])
                } catch {
                    fatalError("Could not create ModelContainer: \(error)")
                }
            }()
            
            return SavedUserProvider(modelContainer: container)
        }.value
    }
    
    func updateUser() {
        print()
        // Runs on main thread
    }
}

Has anyone managed to have SwiftData updates on a background thread with ModelActor ?


Solution

  • As @Rob suggested, prefer using a normal actor over a @ModelActor since calling it from the main thread will execute code on the main thread.

    So here's the solution with a normal actor and a private executor as suggested by Rob:

    actor SavedUserProvider {
        private let modelExecutor: any ModelExecutor
        private let modelContainer: ModelContainer
        private var modelContext: ModelContext { modelExecutor.modelContext }
        
        init(modelContainer: ModelContainer) {
            self.modelExecutor = DefaultSerialModelExecutor(modelContext: ModelContext(modelContainer))
            self.modelContainer = modelContainer
        }
        
        func updateUser() {
            // Will never run on main thread
        }
    }