Xcode 16 RC1(Swift 6)中DispatchQueue.concurrentPerform捕获UnsafeMutablePointer触发警告的解决办法咨询
我最近刚碰到这个Swift 6的新警告,太懂这种看着一堆红警告闹心的感觉了!咱们先来搞明白为啥会出这个问题:Swift 6默认开启了严格的Sendable检查,DispatchQueue.concurrentPerform的闭包是标记为@Sendable的,意思是闭包里捕获的变量必须是线程安全的Sendable类型。但UnsafeMutablePointer是个裸指针,完全没有线程安全保障,编译器自然就会抛出警告,而且Swift 6里这会变成错误,必须解决。
下面给你几个实用的解决思路,你可以根据自己的代码场景选:
包装成@unchecked Sendable类型(适合能保证线程安全的场景)
如果你能确保每个内存地址(也就是每个offset)只会被一个线程访问——就像你现在嵌套concurrentPerform,每个row+column对应的offset都是唯一的,不会有多个线程读写同一块内存——那可以把指针包装成一个标记为@unchecked Sendable的结构体,告诉编译器“我自己能保证安全”:struct UnsafeSendablePointer<T>: @unchecked Sendable { let pointer: UnsafeMutablePointer<T> }然后在调用concurrentPerform前把你的pixelBuffer包一下:
private static func getPixelationPixels( inputCGImage: CGImage, pixelBuffer: UnsafeMutablePointer<RGBA32> ) -> [PixelationPixel] { let width = inputCGImage.width let height = inputCGImage.height let all = width * height var pixels = [PixelationPixel]() pixels.reserveCapacity(all) // 先包装指针 let sendablePointer = UnsafeSendablePointer(pointer: pixelBuffer) DispatchQueue.concurrentPerform(iterations: height) { row in DispatchQueue.concurrentPerform(iterations: width) { column in let offset = row * width + column // 用包装后的指针访问 let c = sendablePointer.pointer[offset].convertToUIColor() locker.withLock { // ... 你的后续代码 } } } return pixels }这里要特别注意:
@unchecked Sendable是“信任开发者”的标记,一定要确保真的没有线程竞争,不然会出现数据错乱甚至崩溃的问题!拷贝到Sendable数组(最安全的方案)
如果不想冒线程安全的风险,或者不确定指针访问的安全性,那可以先把指针里的所有数据拷贝到一个普通的Swift数组里——数组是值类型,天然符合Sendable要求:private static func getPixelationPixels( inputCGImage: CGImage, pixelBuffer: UnsafeMutablePointer<RGBA32> ) -> [PixelationPixel] { let width = inputCGImage.width let height = inputCGImage.height let all = width * height var pixels = [PixelationPixel]() pixels.reserveCapacity(all) // 把指针数据拷贝到数组 let pixelArray = Array(UnsafeBufferPointer(start: pixelBuffer, count: all)) DispatchQueue.concurrentPerform(iterations: height) { row in DispatchQueue.concurrentPerform(iterations: width) { column in let offset = row * width + column // 访问数组元素 let c = pixelArray[offset].convertToUIColor() locker.withLock { // ... 你的后续代码 } } } return pixels }这个方案的唯一缺点是会多一次内存拷贝,如果处理的是超大图像,可能会有一点点性能开销,但胜在绝对安全,编译器也不会再叽叽喳喳。
另外提个小建议:嵌套DispatchQueue.concurrentPerform可能会创建过多的线程,反而影响性能,你可以考虑把二维遍历改成一维的,只调用一次concurrentPerform(iterations: all),然后通过offset计算row和column,这样线程调度会更高效哦。
备注:内容来源于stack exchange,提问作者sabiland

