How do I safely access Core data NSManagedObject attributes from SwiftUI view using the swift concurrency model.

My understanding is we need to enclose any access to NSManageObject attributes in await context.perform {}. But if I use it anywhere in a view be that in the body(), or init() or .task() modifier I get the following warnings or errors:

for .task{} modifier or if within any Task {}:

Passing argument of non-sendable type 'NSManagedObjectContext.ScheduledTaskType' outside of main actor-isolated context may introduce data races this happens even if I create a new context solely for this access.

if within body() or init():

'await' in a function that does not support concurrency

but we cant set body or init() to async

an example of the code is something along the lines of:

var attributeString: String = ""
let context = PersistentStore.shared.persistentContainer.newBackgroundContext()
await context.perform {
    let backgroundObject = context.objectWithID(objectID)
    attributeString = backgroundObject.attribute!
}
2

There are 2 answers

2
malhal On

The trick is to get the nsmanagedObject.objectID before your perform block, then inside perform you just need to find the object again in the background context using that ID. Now you have an object that is safe to access from the background, e.g.


let objectID = nsmanagedObject.objectID
var attributeString: String = ""
let context = PersistentStore.shared.persistentContainer.newBackgroundContext()
await context.perform {
    let backgroundObject = context.objectWithID(objectID)
    attributeString = backgroundObject.attribute!
}
0
Joakim Danielson On

Your code isn't complete but something like this should work. Create a function to perform the fetch, this function is also where you create the background context

func fetchObject(/* params... */) async -> SomeManagedObject {
    let context = PersistentStore.shared.persistentContainer.newBackgroundContext()
    return await context.perform {
         /* fetch */

         return fetchedObject
    }
}
    

and then in your view you call the function and use it

.task {
    let managedObject = await fetchObject(/* params */)
    attributesString = managedObject.attribute