Can SceneKit be used with Swift 6 Concurrency ?

I am trying to port SceneKit projects to Swift 6, and I just can't figure out how that's possible. I even start thinking SceneKit and Swift 6 concurrency just don't match together, and SceneKit projects should - hopefully for the time being only - stick to Swift 5.

The SCNSceneRendererDelegate methods are called in the SceneKit Thread.

If the delegate is a ViewController:

class GameViewController: UIViewController {
    let aNode = SCNNode()

    func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
        aNode.position.x = 10
    }
}

Then the compiler generates the error "Main actor-isolated instance method 'renderer(_:updateAtTime:)' cannot be used to satisfy nonisolated protocol requirement"

Which is fully understandable.

The compiler even tells you those methods can't be used for protocol conformance, unless:

  • Conformance is declare as @preconcurrency SCNSceneRendererDelegate like this:
class GameViewController: UIViewController, @preconcurrency SCNSceneRendererDelegate {

But that just delays the check to runtime, and therefore, crash in the SceneKit Thread happens at runtime...

Again, fully understandable.

  • or the delegate method is declared nonisolated like this:
    nonisolated func renderer(_ renderer: any SCNSceneRenderer, updateAtTime time: TimeInterval) {
        aNode.position.x = 10
    }

Which generates the compiler error: "Main actor-isolated property 'position' can not be mutated from a nonisolated context".

Again fully understandable.

If the delegate is not a ViewController but a nonisolated class, we also have the problem that SCNNode can't be used.

Nearly 100% of the SCNSceneRendererDelegate I've seen do use SCNNode or similar MainActor bound types, because they are meant for that.

So, where am I wrong ? What is the solution to use SceneKit SCNSceneRendererDelegate methods with full Swift 6 compilation ? Is that even possible for now ?

Answered by DTS Engineer in 807719022

I’m not really a SceneKit expert, but I think I can explain one of the oddities you’re seeing:

Which generates the compiler error: "Main actor-isolated property 'position' can not be mutated from a nonisolated context". Again fully understandable.

That doesn’t gel with my understanding of how SceneKit works. I’d fully expect you to modify SCNNode properties from the render callback, which is indeed not running on the main thread.

So I dug into this some more and discovered something interesting. The doc page for SCNNode shows this:

iOS, Mac Catalyst, tvOS, visionOS

@MainActor
class SCNNode : NSObject

macOS, watchOS

class SCNNode : NSObject

So it’s main actor bound on iOS but not macOS. Huh?

Looking through the headers I found that, on iOS, it conforms to the UIFocusItem protocol and that’s main actor bound. So, by the rules of actor-protocol conformance, the entire node is main actor bound on iOS.

This seems… wrong. I encourage you to file a bug against SceneKit about it. Please post your bug number, just for the record.

Unless someone comes up with a better suggestion — hopefully someone who knows more about SceneKit than I do! — I think that you’re right: Sticking with the Swift 5 language mode is the correct option right now.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

I’m not really a SceneKit expert, but I think I can explain one of the oddities you’re seeing:

Which generates the compiler error: "Main actor-isolated property 'position' can not be mutated from a nonisolated context". Again fully understandable.

That doesn’t gel with my understanding of how SceneKit works. I’d fully expect you to modify SCNNode properties from the render callback, which is indeed not running on the main thread.

So I dug into this some more and discovered something interesting. The doc page for SCNNode shows this:

iOS, Mac Catalyst, tvOS, visionOS

@MainActor
class SCNNode : NSObject

macOS, watchOS

class SCNNode : NSObject

So it’s main actor bound on iOS but not macOS. Huh?

Looking through the headers I found that, on iOS, it conforms to the UIFocusItem protocol and that’s main actor bound. So, by the rules of actor-protocol conformance, the entire node is main actor bound on iOS.

This seems… wrong. I encourage you to file a bug against SceneKit about it. Please post your bug number, just for the record.

Unless someone comes up with a better suggestion — hopefully someone who knows more about SceneKit than I do! — I think that you’re right: Sticking with the Swift 5 language mode is the correct option right now.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

bug filed as FB15425116

thanks Quinn for confirming my thoughts.

I just converted my code to Swift 6 today. My SceneKit game kept crashing on launch. I figured out that simply calling this code

ship.runAction(.customAction(duration: 1, action: { _, _ in
}))

causes a crash with Swift 6, while it works with Swift 5. Another sign that SceneKit is not ready for Swift 6.

I filed FB15570385.

Can someone with a real SceneKit project re-test this with the latest Xcode 16.2 beta seed (16.2b3 currently). I took a very quick look there and it seems to be fixed, but I’m not a SceneKit expert so I’d love to get some confirmation.

Oh, and by “this” I mean FB15425116.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I just installed Xcode 16.2 beta 3. I was going to test @Stephane204's issue, but even when using Xcode 16.1 I get a different compiler error for this code:

class GameViewController: UIViewController, SCNSceneRendererDelegate {

    let node = SCNNode()
    
    nonisolated public func renderer(_ renderer: any SCNSceneRenderer, didApplyAnimationsAtTime time: TimeInterval) {
        node.position.x = 0
    }
    
}

I don't get

Main actor-isolated property 'position' can not be mutated from a nonisolated context

but I get

Main actor-isolated property 'node' can not be mutated from a nonisolated context

which I think is a different issue, as node is in fact a property of a main actor-isolated class, so this doesn't seem like a bug to me, although I have to say that I sometimes struggle to understand Swift Concurrency.

Can SceneKit be used with Swift 6 Concurrency ?
 
 
Q