defaultIsolation MainActor and Core Data background tasks

With Xcode 26 defaultIsolation is set to MainActor. I want to do some Core Data stuff in a background thread like this.

Because everything is a MainActor now, even the generated class by the model Item is now a MainActor, which gives me this warning.

Assuming this is the correct way to handle background tasks with this new defaultIsolation:
What, if anything, can I do about that? Should Core Data adopt nonisolated for its auto generated NSManagedObject classes?

nonisolated
struct BackgroundDataHandler {
    @concurrent
    func saveItem() async throws {
        let context = await PersistenceController.shared.container.newBackgroundContext()
                
        try await context.perform {
            let newGame = Item(context: context)
            
            newGame.timestamp = Date.now // Main actor-isolated property 'timestamp' can not be mutated from a nonisolated context; this is an error in the Swift 6 language mode
        
            try context.save()
        }
    }
}

This is up to CoreData developers I think. We cannot decide what they should do.

BTW I think that a timestamp should not be Main-actor isolated, there is no reason for it to do so.

And why is @concurrent attribute not explained in Documentation ? Does it add something to async ?

The @concurrent attribute allows functions on the MainActor to always run in the background in response to the defaultIsolation now being the MainActor for Xcode 26 projects.

As I said, this is the reason that all variables on a NSManagedObject class are now Main Actor-isolated by default. If there's no way around fixing the warning given, which I have no idea about right now, then Apple has to update Core Data, which they have not done yet as of iOS 26 beta 1.

1 Like

Thanks for the video link. I will watch it.

I believe that the Core Data team just don't like the Swift Сoncurrency innovations :)

1 Like

I was under impression that a background execution has nothing to do with main thread. To be honest, what I was expecting from async/await from the beginning, was a syntax sugar over GCD.

From the video: "Interleaving improves system performance" - are we in 1984 inventing Classic MacOS?

That's surely the case looking at the last few years. The annoying part is that this would make Core Data unusable for background tasks, which is kind of bad.

Very bad.

I never liked Core Data anyway. It's just a strange wrapper over SQLite. I think you can find/write better/simpler solutions.

With all due respect to Core Data team of course...

It's just common knowledge to have data organized in tables, and fetch data using SQL queries. I mean, it would be trivial to write your own nonisolated func executeSQL(statement: String) using SQLite API under the hood. Most likely you can find something ready-made like that on GitHub.

Aha. Restoring status quo after introducing SE-0420. Cool.

It’s already nonisolated, the errors you see is for instances inside your class that is isolated to the main actor. CoreData itself, unfortunately, lacks some of the important annotations for new concurrency and given the amount of effort put into SwiftData (which I don’t quite like in comparison to CoreData), not sure if it will be updated properly.

The most convenient way to work with CoreData in the new concurrency system is via actors with custom executors, where custom executor is defined based on NSManagedObjectContext.

For main thread contexts you can either try to define another custom actor or use nonisolated(unsafe) to avoid unnecessary await, since CoreData itself guarantees main thread here.

I think your understanding of SE-0420 is incorrect. It hasn’t changed behaviour introduced by SE-0338.

However, SE-0461 which did introduce @concurrent attribute, does change semantics of nonisolated functions to sticky with calling actor isolation (and relative attribute to opt-out) in order to simplify work with non-Sendable types. And does so behind feature flag.

2 Likes

For main context stuff, this should be enough now that the struct is on the Main Actor, right?

struct MainActorDataHandler {
    func saveItem() async throws {
        let context = PersistenceController.shared.container.viewContext
        
        let newGame = Item(context: context)
        newGame.timestamp = .now
        
        try context.save()
    }
}

Yes, just keep in mind that same rules as previously apply to CoreData context — if it is not on main queue (actor), you need wrap that in perform block. From that perspective, wrapping everything into actors is beneficial — much harder to make a mistake.

So, I've now tried multiple things in relation to actors and also this actor with a custom executor (https://fatbobman.com/en/posts/core-data-reform-achieving-elegant-concurrency-operations-like-swiftdata/). Still, using defaultIsolation MainActor, the warning stays. I think there's a bit more going on here.

When I disable code gen for the managed object class, create it myself and add nonisolated to it, the warning goes away, which makes me think NSManagedObject has no such annotation and now uses MainActor when defaultIsolation it set as such.