Swift 6下Metal的addCompletedHandler引发iOS应用崩溃问题
问题:Swift 6下Metal命令缓冲区完成处理器崩溃原因
这段代码在Swift 5编译环境下可正常运行,但在Swift 6编译后,iOS 18.0模拟器及真机上均会崩溃,注释掉draw方法中的addCompletedHandler代码行即可解决问题,请问原因是什么?
import Metal import MetalKit import UIKit class ViewController: UIViewController { @IBOutlet var metalView: MTKView! private var commandQueue: MTLCommandQueue? override func viewDidLoad() { super.viewDidLoad() guard let device = MTLCreateSystemDefaultDevice() else { fatalError("expected a Metal device") } self.commandQueue = device.makeCommandQueue() metalView.device = device metalView.enableSetNeedsDisplay = true metalView.isPaused = true metalView.delegate = self } } extension ViewController: MTKViewDelegate { func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} func draw(in view: MTKView) { guard let commandQueue, let commandBuffer = commandQueue.makeCommandBuffer() else { return } commandBuffer.addCompletedHandler { _ in } // works with Swift 5, crashes with Swift 6 commandBuffer.commit() } }
堆栈跟踪
Thread 10 Queue : connection Queue (serial) #0 0x000000010581c3f8 in _dispatch_assert_queue_fail () #1 0x000000010581c384 in dispatch_assert_queue () #2 0x00000002444c63e0 in swift_task_isCurrentExecutorImpl () #3 0x0000000104d71ec4 in closure #1 in ViewController.draw(in:) () #4 0x0000000104d71f58 in thunk for @escaping @callee_guaranteed (@guaranteed MTLCommandBuffer) -> () () #5 0x0000000105ef1950 in __47-[CaptureMTLCommandBuffer _preCommitWithIndex:]_block_invoke_2 () #6 0x00000001c50b35b0 in -[MTLToolsCommandBuffer invokeCompletedHandlers] () #7 0x000000019e94d444 in MTLDispatchListApply () #8 0x000000019e94f558 in -[_MTLCommandBuffer didCompleteWithStartTime:endTime:error:] () #9 0x000000019e95352c in -[_MTLCommandQueue commandBufferDidComplete:startTime:completionTime:error:] () #10 0x0000000226ef50b0 in handleMainConnectionReplies () #11 0x00000001800c9690 in _xpc_connection_call_event_handler () #12 0x00000001800cad90 in _xpc_connection_mach_event () #13 0x000000010581a86c in _dispatch_client_callout4 () #14 0x0000000105837950 in _dispatch_mach_msg_invoke () #15 0x0000000105822870 in _dispatch_lane_serial_drain () #16 0x0000000105838c10 in _dispatch_mach_invoke () #17 0x0000000105822870 in _dispatch_lane_serial_drain () #18 0x00000001058237b0 in _dispatch_lane_invoke () #19 0x00000001058301f0 in _dispatch_root_queue_drain_deferred_wlh () #20 0x000000010582f75c in _dispatch_workloop_worker_thread () #21 0x00000001050abb74 in _pthread_wqthread ()
原因及解决方法
崩溃的核心是Swift 6启用了严格的并发执行器验证机制:
- Swift 5中,Metal的命令缓冲区完成处理器会在
connection Queue(系统内部串行队列)上回调闭包,这种跨队列调用不会触发断言。 - 但Swift 6默认要求逃逸闭包必须在它被捕获时的执行器队列上运行,
swift_task_isCurrentExecutorImpl会检查当前队列是否匹配预期执行器,不匹配就触发dispatch_assert_queue_fail断言崩溃。
解决方法
- 允许闭包在任意队列执行:给闭包添加
@Sendable标注,明确告知Swift该闭包可在任意队列安全执行:
commandBuffer.addCompletedHandler { @Sendable _ in // 你的完成逻辑 }
- 手动切换到指定队列执行:如果需要在特定队列(比如主队列)处理完成逻辑,手动调度到目标队列:
commandBuffer.addCompletedHandler { _ in DispatchQueue.main.async { // 主队列上的处理逻辑 } }
- 用脱离执行器的任务包裹逻辑:如果闭包内有复杂并发操作,用
Task.detached脱离当前执行器限制:
commandBuffer.addCompletedHandler { [weak self] _ in Task.detached { // 此处逻辑不受原执行器队列限制 } }
内容的提问来源于stack exchange,提问作者grfryling
相关产品推荐
相关产品推荐

