SKAction.removeFromParent() causes crash when run in SCNView.overlaySKScene on iOS

Even when the action is run on the main thread, the following code causes a crash on iOS, but not on macOS. The game launches with a simple yellow rectangle, and when it finishes fading out and should be removed from the overlay scene, the app crashes.

The code can be pasted into the file GameController.swift of Xcode's default project for Multiplatform macOS and iOS game.

import SceneKit
import SpriteKit

@MainActor
class GameController: NSObject {

    let scene: SCNScene
    let sceneRenderer: SCNSceneRenderer
    
    init(sceneRenderer renderer: SCNSceneRenderer) {
        sceneRenderer = renderer
        scene = SCNScene(named: "Art.scnassets/ship.scn")!
        
        super.init()
        
        sceneRenderer.scene = scene
        
        renderer.overlaySKScene = SKScene(size: CGSize(width: 500, height: 500))
        
        DispatchQueue.main.async {
            let node = SKShapeNode(rect: CGRect(x: 100, y: 100, width: 100, height: 100))
            node.fillColor = .yellow
            node.run(.sequence([
                .fadeOut(withDuration: 1),
                .removeFromParent()
            ]))
            renderer.overlaySKScene!.addChild(node)
        }
    }

}

The Xcode console shows this stacktrace:

*** Assertion failure in -[UIApplication _performAfterCATransactionCommitsWithLegacyRunloopObserverBasedTiming:block:], UIApplication.m:3246
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Call must be made on main thread'
*** First throw call stack:
(
	0   CoreFoundation                      0x00000001804ae0f8 __exceptionPreprocess + 172
	1   libobjc.A.dylib                     0x0000000180087db4 objc_exception_throw + 56
	2   Foundation                          0x0000000180d17058 _userInfoForFileAndLine + 0
	3   UIKitCore                           0x00000001853cf678 -[UIApplication _performAfterCATransactionCommitsWithLegacyRunloopObserverBasedTiming:block:] + 376
	4   UIKitCore                           0x000000018553f7a0 -[_UIFocusUpdateThrottle scheduleProgrammaticFocusUpdate] + 300
	5   UIKitCore                           0x0000000184e2e22c -[UIFocusSystem _requestFocusUpdate:] + 548
	6   UIKitCore                           0x0000000184e2dfa4 -[UIFocusSystem requestFocusUpdateToEnvironment:] + 76
	7   UIKitCore                           0x0000000184e2e864 -[UIFocusSystem _focusEnvironmentWillDisappear:] + 408
	8   SpriteKit                           0x00000001a3d472f4 _ZL12_removeChildP6SKNodeS0_P7SKScene + 240
	9   SpriteKit                           0x00000001a3d473b0 -[SKNode removeChild:] + 80
	10  SpriteKit                           0x00000001a3d466b8 -[SKNode removeFromParent] + 128
	11  SpriteKit                           0x00000001a3d1678c -[SKRemove updateWithTarget:forTime:] + 64
	12  SpriteKit                           0x00000001a3d1b740 _ZN11SKCSequence27cpp_updateWithTargetForTimeEP7SKCNoded + 84
	13  SpriteKit                           0x00000001a3d20e3c _ZN7SKCNode6updateEdf + 156
	14  SpriteKit                           0x00000001a3d20f20 _ZN7SKCNode6updateEdf + 384
	15  SpriteKit                           0x00000001a3d26fb8 -[SKScene _update:] + 464
	16  SpriteKit                           0x00000001a3cf3168 -[SKSCNRenderer _update:] + 80
	17  SceneKit                            0x000000019c932bf0 -[SCNMTLRenderContext renderSKSceneWithRenderer:overlay:atTime:] + 60
	18  SceneKit                            0x000000019c9ebd98 -[SCNRenderer _drawOverlaySceneAtTime:] + 204
	19  SceneKit                            0x000000019cb1a1c0 _ZN3C3D11OverlayPass7executeERKNS_10RenderArgsE + 60
	20  SceneKit                            0x000000019c8e05ec _ZN3C3D13__renderSliceEPNS_11RenderGraphEPNS_10RenderPassERtRKNS0_9GraphNodeERPNS0_5StageENS_10RenderArgsEbRPU27objcproto16MTLCommandBuffer11objc_object + 2660
	21  SceneKit                            0x000000019c8e18ac _ZN3C3D11RenderGraph7executeEv + 3808
	22  SceneKit                            0x000000019c9ed26c -[SCNRenderer _renderSceneWithEngineContext:sceneTime:] + 756
	23  SceneKit                            0x000000019c9ed544 -[SCNRenderer _drawSceneWithNewRenderer:] + 208
	24  SceneKit                            0x000000019c9ed9fc -[SCNRenderer _drawScene:] + 40
	25  SceneKit                            0x000000019c9edce4 -[SCNRenderer _drawAtTime:] + 500
	26  SceneKit                            0x000000019ca87950 -[SCNView _drawAtTime:] + 368
	27  SceneKit                            0x000000019c943b74 __83-[NSObject(SCN_DisplayLinkExtensions) SCN_setupDisplayLinkWithQueue:screen:policy:]_block_invoke + 44
	28  SceneKit                            0x000000019ca50600 -[SCNDisplayLink _displayLinkCallbackReturningImmediately] + 132
	29  libdispatch.dylib                   0x000000010239173c _dispatch_client_callout + 16
	30  libdispatch.dylib                   0x0000000102394c14 _dispatch_continuation_pop + 756
	31  libdispatch.dylib                   0x00000001023aa4e0 _dispatch_source_invoke + 1736
	32  libdispatch.dylib                   0x00000001023997f0 _dispatch_lane_serial_drain + 340
	33  libdispatch.dylib                   0x000000010239a774 _dispatch_lane_invoke + 420
	34  libdispatch.dylib                   0x00000001023a71a8 _dispatch_root_queue_drain_deferred_wlh + 324
	35  libdispatch.dylib                   0x00000001023a6604 _dispatch_workloop_worker_thread + 488
	36  libsystem_pthread.dylib             0x000000010242bb74 _pthread_wqthread + 284
	37  libsystem_pthread.dylib             0x000000010242a934 start_wqthread + 8
)
libc++abi: terminating due to uncaught exception of type NSException

Am I doing something wrong?

Hello @Nickkk, would you mind filing a bug report via Feedback Assistant?

I filed FB15498049.

It doesn't crash for me on simulator or device.

I just tried it again on Simulator running iOS 18.0 and it still crashes, but on my iPad Pro 11” 2018 with iOS 18.0.1 it doesn’t crash... at least not the sample project. My other project still crashes from time to time when an element is removed from the parent, though I haven't checked yet what the difference is.

Yes, it does crash on the iPad simulator. It didn't crash for me on the iPhone simulator. Try this:

let node = SKShapeNode(rect: CGRect(x: 100, y: 100, width: 100, height: 100))
node.fillColor = .yellow
            
let removeAction = SKAction.run {
     DispatchQueue.main.async {
         node.removeFromParent()
    }
 }
            
node.run(.sequence([ .fadeOut(withDuration: 5), removeAction]))

renderer.overlaySKScene!.addChild(node)
SKAction.removeFromParent() causes crash when run in SCNView.overlaySKScene on iOS
 
 
Q