Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

Xcode 26 - New Swift 6.2 Concurrency Sendable Closure Problems

I'm running into a seemingly unsolvable compile problem with the new Xcode 26 and Swift 6.2.

Here's the issue.

I've got this code that was working before:

NSAnimationContext.runAnimationGroup({(context) -> Void in
	context.duration = animated ? 0.5 : 0
	clipView.animator().setBoundsOrigin(p)
}, completionHandler: {
	self.endIgnoreFrameChangeEvents()
})

It's very simple. The clipView is a scrollView.contentView, and "animated" is a bool, and p is an NSPoint

It captures those things, scrolls the clip view (animating if needed) to the point, and then calls a method in self to signal that the animation has completed.

I'm getting this error:

Call to main actor-isolated instance method 'endIgnoreFrameChangeEvents()' in a synchronous nonisolated context

So, I don't understand why so many of my callbacks are getting this error now, when they worked before, but it is easy to solve. There's also an async variation of runAnimationGroup. So let's use that instead:

Task {
	await NSAnimationContext.runAnimationGroup({(context) -> Void in
		context.duration = animated ? 0.5 : 0
		clipView.animator().setBoundsOrigin(p)
	})
	
	self.endIgnoreFrameChangeEvents()
}

So, when I do this, then I get a new error. Now it doesn't like the first enclosure. Which it was perfectly happy with before.

Here's the error: Sending value of non-Sendable type '(NSAnimationContext) -> Void' risks causing data races

Here are the various overloaded definitions of runAnimationGroup:

    open class func runAnimationGroup(_ changes: (NSAnimationContext) -> Void, completionHandler: (@Sendable () -> Void)? = nil)

    @available(macOS 10.7, *)
    open class func runAnimationGroup(_ changes: (NSAnimationContext) -> Void) async

    @available(macOS 10.12, *)
    open class func runAnimationGroup(_ changes: (NSAnimationContext) -> Void)

The middle one is the one that I'm trying to use. The closure in this overload isn't marked sendable. But, lets try making it sendable now to appease the compiler, since that seems to be what the error is asking for:

Task {
	await NSAnimationContext.runAnimationGroup({ @Sendable (context) -> Void in
		context.duration = animated ? 0.5 : 0
		clipView.animator().setBoundsOrigin(p)
	})
	
	self.endIgnoreFrameChangeEvents()
}

So now I get errors in the closure itself. There are 2 errors, only one of which is easy to get rid of.

Call to main actor-isolated instance method 'animator()' in a synchronous nonisolated context

Call to main actor-isolated instance method 'setBoundsOrigin' in a synchronous nonisolated context

So I can get rid of that first error by capturing clipView.animator() outside of the closure and capturing the animator. But the second error, calling setBoundsOrigin(p) - I can't move that outside of the closure, because that is the thing I am animating! Further, any property you're going to me animating in runAnimationGroup is going to be isolated to the main actor.

So now my code looks like this, and I'm stuck with this last error I can't eliminate:

let animator = clipView.animator()

Task {
	await NSAnimationContext.runAnimationGroup({ @Sendable (context) -> Void in
		context.duration = animated ? 0.5 : 0
		animator.setBoundsOrigin(p)
	})
	
	self.endIgnoreFrameChangeEvents()
}

Call to main actor-isolated instance method 'setBoundsOrigin' in a synchronous nonisolated context

There's something that I am not understanding here that has changed about how it is treating closures. This whole thing is running synchronously on the main thread anyway, isn't it? It's being called from a MainActor context in one of my NSViews. I would expect the closure in runAnimationGroup would need to be isolated to the main actor, anyway, since any animatable property is going to be marked MainActor.

How do I accomplish what I am trying to do here?

One last note: There were some new settings introduced at WWDC that supposedly make this stuff simpler - "Approchable Concurrency". In this example, I didn't have that turned on. Turning it on and setting the default to MainActor does not seem to have solved this problem.

(All it does is cause hundreds of new concurrency errors in other parts of my code that weren't there before!)

This is the last new error in my code (without those settings), but I can't see any way around this one. It's basically the same error as the others I was getting (in the callback closures), except with those I could eliminate the closures by changing APIs.

Just as an update - I took my changes back to Xcode 16.4 and got the same errors when I tried to use the asynchronous version of runAnimationGroup.

So, I don't understand how I am supposed to use the async version of this method. Maybe the async version actually runs its closure on a thread?

But back to the original error, which is the thing that started this all off:

NSAnimationContext.runAnimationGroup({(context) -> Void in
	context.duration = animated ? 0.5 : 0
	clipView.animator().setBoundsOrigin(p)
}, completionHandler: {
	self.endIgnoreFrameChangeEvents()
})

Call to main actor-isolated instance method 'endIgnoreFrameChangeEvents()' in a synchronous nonisolated context

This is the error that I get in 6.2 / Xcode 26 that I wasn't getting before.

I would expect all of these callback-based APIs to run their completion handlers on the main thread. It seems like maybe there's just a @MainActor missing in the definition of completionHandler: here? It didn't seem like this was necessary before Swift 6.2.

One more update- I was able to work around this for now just by tossing an assumeIsolated into the completion handler. Hopefully, Apple will eventually annotate all of these completion handlers as explicitly @MainActor, now that the Swift language has evolved and the completion handler isn't implicitly MainActor like it used to be.

I'm still not sure how one would use the async version of this API, though. It either should have had its closure marked as @MainActor but didn't, or if it DOES need to be nonisolated (because it runs on a thread), that means for animating view properties we have to use the callback version of this API.

Here is my final, working (for now) code. I hope Apple annotates these completion closures better so the original code continues to work.

NSAnimationContext.runAnimationGroup({(context) -> Void in
			context.duration = animated ? 0.5 : 0
			clipView.animator().setBoundsOrigin(p)
		}, completionHandler: {
			MainActor.assumeIsolated {
				self.endIgnoreFrameChangeEvents()
			}
		})

Try this instead:

Task
	{
	await withCheckedContinuation
		{
		continuation in
		
		NSAnimationContext.runAnimationGroup
			{
			[self] context in

			context.duration = animated ? 0.5 : 0
			clipView.animator().setBoundsOrigin(p)
			}
		completionHandler:
			{
			continuation.resume()
			}
		}

	endIgnoreFrameChangeEvents()
	}

To clarify, I think completionHandler on runAnimationGroup should specify that the closure is sendable. It seems like that would fix the problem.

But regardless, the above code will work and is backwards compatible.

I too, am running into this problem. I have a full reproducible example for something that works in XCode 16.4 but does not work in XCode 26, Beta 1.

protocol TestProtocol:Sendable {
    var version: Int { get }
}

actor TestManager {
    private let migrations: TestProtocol
    
    init(migrations: TestProtocol) {
        self.migrations = migrations
        if migrations.version < 5 {
            
        }
    }
    
    func doSomething() async throws {
        if migrations.version < 5 {
            
        }
    }
}

This gives me:

Main actor-isolated property 'version' can not be referenced from a nonisolated context; this is an error in the Swift 6 language mode

I can store the property, then await the value before my check - which I guess is fine...

    func doSomething() async throws {
        let ver = await migrations.version
        if ver < 5 {
            
        }
    }

Though this is a PITA when doing logging all over the app, or constantly accessing members of the object itself. For example, the attached screenshot shows I can't access version - despite the fact that the DatabaseMigration protocol is Sendable. Even more confusing for me is that I await a call to migration.build() and yet I still can't access member variables on the results of the awaited function...

If I run this code in XCode 16.4, there's no actor-isolated warnings for Swift 6, even on a file->new project. I can't tell if this is a bug in XCode 26, or not. If it's not, I don't know what the expected developer workflow is for this going forward.

Xcode 26 - New Swift 6.2 Concurrency Sendable Closure Problems
 
 
Q