最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - SwiftDataCoreData: 'Sending context risks causing data races' when sharing contexts between actors - Sta

programmeradmin2浏览0评论

In my app, I've been using ModelActors in SwiftData, and using actors with a custom executor in Core Data to create concurrency safe services.

I have multiple actor services that relate to different data model components or features, each that have their own internally managed state (DocumentService, ImportService, etc).

The problem I've ran into, is that I need to be able to use multiple of these services within another service, and those services need to share the same context. Swift 6 doesn't allow passing contexts across actors.

The specific problem I have is that I need a master service that makes multiple unrelated changes without saving them to the main context until approved by the user.

I've tried to find a solution in SwiftData and Core Data, but both have the same problem which is contexts are not sendable. Read the comments in the code to see the issue:

/// This actor does multiple things without saving, until committed in SwiftData.
@ModelActor
actor DatabaseHelper {
    func commitChange() throws {
        try modelContext.save()
    }
    
    func makeChanges() async throws {
        // Do unrelated expensive tasks on the child context...
        
        // Next, use our item service
        let service = ItemService(modelContainer: SwiftDataStack.shared.container)
        let id = try await service.expensiveBackgroundTask(saveChanges: false)
        
        // Now that we've used the service, we need to access something the service created.
        // However, because the service created its own context and it was never saved, we can't access it.
       let itemFromService = context.fetch(id) // fails
        
        // We need to be able to access changes made from the service within this service, 
        /// so instead I tried to create the service by passing the current service context, however that results in:
        // ERROR: Sending 'self.modelContext' risks causing data races
        let serviceFromContext = ItemService(context: modelContext)
        
        // Swift Data doesn't let you create child contexts, so the same context must be used in order to change data without saving. 
    }
}

@ModelActor
actor ItemService {
    init(context: ModelContext) {
        modelContainer = SwiftDataStack.shared.container
        modelExecutor = DefaultSerialModelExecutor(modelContext: context)
    }
    
    func expensiveBackgroundTask(saveChanges: Bool = true) async throws -> PersistentIdentifier? {
        // Do something expensive...
        return nil
    }
}

Core Data has the same problem:

/// This actor does multiple things without saving, until committed in Core Data.
actor CoreDataHelper {
    let parentContext: NSManagedObjectContext
    let context: NSManagedObjectContext
    
    /// In Core Data, I can create a child context from a background context.
    /// This lets you modify the context and save it without updating the main context.
    init(progress: Progress = Progress()) {
        parentContext = CoreDataStack.shared.newBackgroundContext()
        let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        childContext.parent = parentContext
        self.context = childContext
    }
    
    /// To commit changes, save the parent context pushing them to the main context.
    func commitChange() async throws {
        // ERROR: Sending 'self.parentContext' risks causing data races
        try await parentContext.perform {
            try self.parentContext.save()
        }
    }

    func makeChanges() async throws {
        // Do unrelated expensive tasks on the child context...

        // As with the Swift Data example, I am unable to create a service that uses the current actors context from here.
        // ERROR: Sending 'self.context' risks causing data races
        let service = ItemService(context: self.context)
    }
}

Am I going about this wrong, or is there a solution to fix these errors?

Some services are huge and have their own internal state. In the master service I might need to create multiple instances of the same service, so it would be impossible to create a single master service. I also am using Core Data, and SwiftData extensively so I need a solution for both.

I seem to have trapped myself into a corner trying to make everything concurrency save, so any help would be appreciated!

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论