Swift 6中RealityKit传递MeshDescriptor引发数据竞争风险问题
Swift 6中拆分MeshResource转换函数引发的数据竞争警告及解决方法
问题背景
原本有一个用于转换网格并返回MeshResource的函数,运行正常:
extension MeshResource { func transformedMesh() async -> MeshResource { var allDescriptors: [MeshDescriptor] = [] for model in self.contents.models { for part in model.parts { var desc = MeshDescriptor(name: part.id) // 构建描述符数据 // desc.positions = ... // desc.normals = ... // desc.textureCoordinates = ... allDescriptors += [desc] } } let mesh = try! await MeshResource(from: allDescriptors) return mesh } }
为了优化性能,将其拆分为两个函数:先提前生成网格描述符,再按需构建MeshResource:
extension MeshResource { struct MeshInfo { let allDescriptors: [MeshDescriptor] } func transformedMeshInfo() async -> MeshInfo { var allDescriptors: [MeshDescriptor] = [] for model in self.contents.models { for part in model.parts { var desc = MeshDescriptor(name: part.id) // 构建描述符数据 // desc.positions = ... // desc.normals = ... // desc.textureCoordinates = ... allDescriptors += [desc] } } return MeshInfo(allDescriptors: allDescriptors) } func transformedMesh(info: MeshInfo) async -> MeshResource { return try! await MeshResource(from: info.allDescriptors) } }
但在Swift 6编译时出现以下错误:
Sending main actor-isolated 'info.allDescriptors' to @concurrent initializer 'init(from:)' risks causing data races between @concurrent and main actor-isolated uses
更新后的完整可复现代码(默认Actor为主Actor):
import RealityKit extension MeshResource { nonisolated struct MeshInfo { let allDescriptors: [MeshDescriptor] } func transformedMeshInfo() -> MeshInfo { var allDescriptors: [MeshDescriptor] = [] for model in self.contents.models { for part in model.parts { var desc = MeshDescriptor(name: part.id) // 构建描述符数据 // desc.positions = ... // desc.normals = ... // desc.textureCoordinates = ... allDescriptors += [desc] } } return MeshInfo(allDescriptors: allDescriptors) } nonisolated static func transformedMesh(info: MeshInfo) async -> MeshResource { return try! await MeshResource(from: info.allDescriptors) } } class MyModelEntity: Entity { var info: MeshResource.MeshInfo! var transformedModel: ModelComponent? required init(originalModel: ModelComponent) { super.init() self.info = originalModel.mesh.transformedMeshInfo() } required init() { fatalError("init() has not been implemented") } func inflate() async { let newMesh = await MeshResource.transformedMesh(info: info) transformedModel = ModelComponent(mesh: newMesh, materials: []) } }
错误原因
Swift 6的并发安全模型更加严格:
MeshResource默认绑定到主Actor,其扩展中的成员默认继承主Actor隔离属性,包括最初未标记nonisolated的MeshInfo结构体。MeshResource(from:)是标记为@concurrent的初始化器,会在并发上下文执行。- 当主Actor隔离的
info.allDescriptors被传递到并发初始化器时,编译器检测到风险:主Actor和并发上下文可能同时访问这些数据,存在数据竞争的可能。
即使后续给MeshInfo添加了nonisolated,但transformedMeshInfo()函数仍属于主Actor隔离,生成的MeshInfo实例仍会被视为主Actor上下文的一部分,传递给并发初始化器时仍会触发警告。
解决方法
核心思路是让存储描述符的MeshInfo和相关处理函数脱离主Actor隔离,确保并发上下文可以安全访问,同时利用Swift值类型的特性避免共享访问风险。
修正后的完整代码
import RealityKit extension MeshResource { // 明确标记为非隔离结构体,脱离主Actor上下文 nonisolated struct MeshInfo { let allDescriptors: [MeshDescriptor] } // 标记为nonisolated,确保可在非主Actor上下文调用 nonisolated func transformedMeshInfo() -> MeshInfo { var allDescriptors: [MeshDescriptor] = [] for model in self.contents.models { for part in model.parts { var desc = MeshDescriptor(name: part.id) // 填充描述符数据 // desc.positions = ... // desc.normals = ... // desc.textureCoordinates = ... allDescriptors.append(desc) } } return MeshInfo(allDescriptors: allDescriptors) } // 静态函数标记为nonisolated,支持并发上下文调用 nonisolated static func transformedMesh(info: MeshInfo) async throws -> MeshResource { // 替换try!为try并添加错误处理,避免运行时崩溃 return try await MeshResource(from: info.allDescriptors) } } class MyModelEntity: Entity { var info: MeshResource.MeshInfo! var transformedModel: ModelComponent? required init(originalModel: ModelComponent) { super.init() // transformedMeshInfo是非隔离函数,可直接在初始化器调用 self.info = originalModel.mesh.transformedMeshInfo() } required init() { fatalError("init() has not been implemented") } func inflate() async { do { let newMesh = try await MeshResource.transformedMesh(info: info) transformedModel = ModelComponent(mesh: newMesh, materials: []) } catch { print("网格创建失败:\(error)") } } }
关键修正点
- 标记
nonisolated:给MeshInfo结构体、transformedMeshInfo()函数、transformedMesh静态函数都添加nonisolated标记,让它们脱离主Actor隔离,允许在并发上下文安全访问。 - 错误处理:将
try!改为try并添加do-catch块,避免因网格初始化失败导致的运行时崩溃。 - 利用值类型特性:
MeshInfo是值类型,传递给transformedMesh时会进行复制,不存在多上下文共享访问的问题,彻底消除数据竞争风险。
内容的提问来源于stack exchange,提问作者HL666
相关产品推荐
相关产品推荐

