Swift Concurrency, Core Data, "partially thread-safe" types & Sendable conformance

Hi,

Xcode warns me that NSPersistentContainer, NSManagedObjectContext, NSPersistentHistoryTransaction and NSPersistentHistoryToken are non-Sendable types and therefore cannot cross actor boundaries.

These warnings occur even when I use @preconcurrency import CoreData (only works with Xcode 14.0 Beta, in Xcode 13.4.1, it says '@preconcurrency' attribute on module 'CoreData' is unused)

I understand that types in Core Data have yet to be marked as Sendable when it makes sense.

Although NSPersistentHistoryTransaction, and NSPersistentHistoryToken are reference types, they should qualify to be marked as Sendable in the future since these are immutable types, am I right?

NSPersistentContainer provides variables and methods like viewContext and newBackgroundContext(). It would make sense that this type is thread-safe. However, I'm not sure about it, especially regarding its loadPersistentStores(completionHandler:) method. Is NSPersistentContainer (and its subclass NSPersistentCloudKitContainer) thread-safe and should it be marked as Sendable in the future?

The case of and NSManagedObjectContext confuses me the most. Indeed, it has both thread-safe methods like perform(_:) and thread-unsafe methods like save(). Still, it should be possible to cross actor boundaries. Would such a "partially thread-safe" type quality to be marked as Sendable? If not, how can it cross actor boundaries?

The reason I'm asking these questions is that I have a CoreDataStack type. It has mutable variables, such as var lastHistoryToken: NSPersistentHistoryToken?, needs to perform work without blocking the main thread, such as writing the token to a file, has async methods, uses notification observers, and needs to be marked as Sendable to be used by other controllers running on different actors.

Since this type is related to back-end work, I made it an Actor. This type exposes the persistent container, and a method to create new background threads. However, I need to explicitly annotate all methods using await context.perform { ... } as nonisolated in this actor. Not doing so causes a data race to be detected at runtime. Is this a bug, and is making a Core Data stack an Actor the proper way to do it or is there a better solution?

Any help would be greatly appreciated.

Thanks.

Xcode Configuration

Xcode 13.4.1, Xcode 14.0 beta 5

The Xcode project is configured with Other Swift Flags set to -Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks.

Post not yet marked as solved Up vote post of Romain Down vote post of Romain
2.4k views
  • You're fighting an unwindable battle here. If you're just learning Core Data, you should avoid trying to marry it to Swift concurrency. Core Data has its own, very specific, means of handling background thread implementations, in that, all managed objects should only be retrieved, changed, and saved within a managed object context perform { } block.

  • I've been dealing with the same issues as @Romain. If NSManagedObjectContext uses its own means of serializing access, doesn't that make it Sendable, assuming you follow the policy of wrapping operations in a perform block?

  • I wouldn't put a core data stack in an actor since it is designed to be used from the main actor. You could perhaps create a background context in an actor though.

Add a Comment

Replies

The more I ( try to ) use CoreData and the more I read about it, the more I think it should be deprecated and replaced with a modern Swift version of LINQ that plays nicely with SwiftUI and concurrency