Nested method calls in `context.perform` with Swift 6

I'm calling a method with the context as parameter, within the context's perform block – is this really not legal in Swift 6?

actor MyActor {
func bar(context: NSManagedObjectContext) { /* some code */ }
func foo(context: NSManagedObjectContext) {
context.performAndWait {
self.bar(context: context)
// WARN: Sending 'context' risks causing data races; this is an error in the Swift 6 language mode
// 'self'-isolated 'context' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
// Access can happen concurrently
}
}
}

The warning appears when I call a method with a context parameter, within the performAndWait-block.

Background: In my app I have methods that takes in API data, and I need to call the same methods from multiple places with the same context to store it, and I do not want to copy paste the code and have hundreds of lines of duplicate code.

Is there a well-known "this is how you should do it" for situations like this?


This is related to a previous post I made, but it's a bit flimsy and got no response: https://developer.apple.com/forums/thread/770605

Answered by DTS Engineer in 825136022

The compiler gives you a warning / error because NSManagedObjectContext is not Sendable. Swift concurrency check doesn't allow passing an un-sendable type across actors because that can trigger a race.

If you have an actor that interacts with other actos via Core Data objects, consider using NSPersistentContainer and NSManagedObjectID, which are sendable. For example:

actor MyActor {
private let context: NSManagedObjectContext
init(persistentContainer: NSPersistentContainer) {
context = persistentContainer.newBackgroundContext()
}
private func bar(objectID: NSManagedObjectID) {
context.performAndWait {
let object = context.object(with: objectID)
/* Work on object as needed.*/
print(object)
}
}
func foo(objectID: NSManagedObjectID) {
context.performAndWait {
self.bar(objectID: objectID)
}
}
}

You can then use the actor in the following way:

func runMyActorBar() {
Task {
let myActor = MyActor(persistentContainer: PersistenceController.shared.container)
await myActor.foo(objectID: items[0].objectID)
}
}

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

The compiler gives you a warning / error because NSManagedObjectContext is not Sendable. Swift concurrency check doesn't allow passing an un-sendable type across actors because that can trigger a race.

If you have an actor that interacts with other actos via Core Data objects, consider using NSPersistentContainer and NSManagedObjectID, which are sendable. For example:

actor MyActor {
private let context: NSManagedObjectContext
init(persistentContainer: NSPersistentContainer) {
context = persistentContainer.newBackgroundContext()
}
private func bar(objectID: NSManagedObjectID) {
context.performAndWait {
let object = context.object(with: objectID)
/* Work on object as needed.*/
print(object)
}
}
func foo(objectID: NSManagedObjectID) {
context.performAndWait {
self.bar(objectID: objectID)
}
}
}

You can then use the actor in the following way:

func runMyActorBar() {
Task {
let myActor = MyActor(persistentContainer: PersistenceController.shared.container)
await myActor.foo(objectID: items[0].objectID)
}
}

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Nested method calls in `context.perform` with Swift 6
 
 
Q