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 ?
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"