You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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断言崩溃。

解决方法

  1. 允许闭包在任意队列执行:给闭包添加@Sendable标注,明确告知Swift该闭包可在任意队列安全执行:
commandBuffer.addCompletedHandler { @Sendable _ in
    // 你的完成逻辑
}
  1. 手动切换到指定队列执行:如果需要在特定队列(比如主队列)处理完成逻辑,手动调度到目标队列:
commandBuffer.addCompletedHandler { _ in
    DispatchQueue.main.async {
        // 主队列上的处理逻辑
    }
}
  1. 用脱离执行器的任务包裹逻辑:如果闭包内有复杂并发操作,用Task.detached脱离当前执行器限制:
commandBuffer.addCompletedHandler { [weak self] _ in
    Task.detached {
        // 此处逻辑不受原执行器队列限制
    }
}

内容的提问来源于stack exchange,提问作者grfryling

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 03:35:08