1

I have the following code:

import UIKit import SpriteKit class ViewController: UIViewController { let textureAtlas: SKTextureAtlas required init?(coder: NSCoder) { textureAtlas = SKTextureAtlas(dictionary: ["Foo": UIImage(named: "foo.png")!]) super.init(coder: coder) textureAtlas.preload { } } } 

You can add a random foo.png file to your project. Then running it with Swift 6 mode will crash:

Thread 4 Queue : com.apple.spritekit.preloadQueue (concurrent) #0 0x00000001099476f5 in _dispatch_assert_queue_fail () #1 0x000000010994768f in dispatch_assert_queue () #2 0x00007ffc10f5f251 in swift_task_isCurrentExecutorImpl () #3 0x0000000109e05f39 in closure #1 in ViewController.init(coder:) () #4 0x0000000109e05fa8 in thunk for @escaping @callee_guaranteed () -> () () #5 0x0000000109944b3d in _dispatch_call_block_and_release () #6 0x0000000109945ec6 in _dispatch_client_callout () #7 0x00000001099490f3 in _dispatch_continuation_pop () #8 0x0000000109947f20 in _dispatch_async_redirect_invoke () #9 0x0000000109959d2c in _dispatch_root_queue_drain () #10 0x000000010995a8ef in _dispatch_worker_thread2 () #11 0x00000001093c4b43 in _pthread_wqthread () #12 0x00000001093c3acf in start_wqthread () Enqueued from com.apple.spritekit.preloadQueue (Thread 4) Queue : com.apple.spritekit.preloadQueue (serial) #0 0x0000000109946bf1 in _dispatch_group_wake () #1 0x0000000109949239 in _dispatch_continuation_pop () #2 0x0000000109947f20 in _dispatch_async_redirect_invoke () #3 0x0000000109959d2c in _dispatch_root_queue_drain () #4 0x000000010995a8ef in _dispatch_worker_thread2 () #5 0x00000001093c4b43 in _pthread_wqthread () #6 0x00000001093c3acf in start_wqthread () 

I have tried:

  • Use Swift 5, with strict concurrency checking. No crash, no warning.
  • Keep Swift 6, but remove the preload call. No crash. However, I need this preload for performance reason during the game.

I am not sure why it crashes here. The stack trace is assembly code.

3
  • "Swift Concurrency assumes that the closure will get called on the main queue, because the closure isn't marked @Sendable" Why does swift concurrency assumes it will get called on main queue? I didn't specify the main queue anywhere. Commented Aug 28, 2024 at 17:13
  • Do you mean the closure was created on the init of vc, which is in main actor? Commented Aug 29, 2024 at 0:42
  • Just tried out textureAtlas.preload { @Sendable in } and it worked! Commented Aug 29, 2024 at 3:39

1 Answer 1

5

Judging from the crash report alone, it seems like a bug in SpriteKit. The completion handler closure gets called on the queue com.apple.spritekit.preloadQueue, but Swift Concurrency doesn't know that.

Swift Concurrency assumes that the closure will get called on the same concurrency context as where preload is called, because the closure isn't marked @Sendable.

Presumably the Swift 6 compiler inserts a check at the start of the closure to check if it is running in the correct concurrency context (the swift_task_isCurrentExecutorImpl line in the trace), but in Swift 5 mode, this check either doesn't exist, or it is designed to not crash to match the previous behaviour.

Marking the closure @Sendable fixes the crash.

textureAtlas.preload { @Sendable in } 

Alternatively, use the async variant if you are in an async context.

// e.g. in a Task { ... } await textureAtlas.preload() 

It seems like other completion handlers in Objective-C APIs are all marked @Sendable, like AVAsset.loadTracks, URLSession.dataTask, etc. The SpriteKit team probably forgot to do the same for preload.

Sign up to request clarification or add additional context in comments.

4 Comments

SpriteKIt team updates their framework, should we remove this @Sendable? Or should we keep it? I want to make sure to check again in future iOS releases.
@OMGPOP When this is fixed, keeping @Sendable won't break anything. It's just redundant, like the : Int in let x: Int = 1.
wait, if we remove @Sendable, will it be compiler error, because we are passing a non-sendable closure to a func param that expects sendable
@OMGPOP No. The sendability of closures can be inferred from the type of the parameter.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.