如何在Mutex结合属性包装器的场景下支持非Sendable值?
适配非Sendable类型的Atomic属性包装器解决方案
问题背景
现有Atomic属性包装器代码:
@available(iOS 18.0, *) @propertyWrapper public final class Atomic<Value: Sendable>: Sendable { private let mutex: Mutex<Value> public init(wrappedValue: Value) { self.mutex = Mutex(wrappedValue) } public var wrappedValue: Value { get { mutex.withLock { $0 } } set { mutex.withLock { $0 = newValue }} } public var projectedValue: Atomic<Value> { self } }
游戏中使用示例:
@Atomic private(set) var cameraTransform: Transform = .identity
当前包装器要求Value遵循Sendable,但需要保护非Sendable类型的数据。直接使用Mutex可行(仅对闭包参数/返回值有sending要求),但属性包装器无法用sending修饰wrappedValue的get/set,因此需要变通方案。
方案1:闭包式安全访问(推荐)
放弃直接通过wrappedValue暴露非Sendable值,转而通过包装器提供带sending闭包的withLock方法,所有操作在锁保护下完成,同时放宽泛型约束:
@available(iOS 18.0, *) @propertyWrapper public final class Atomic<Value> { private let mutex: Mutex<Value> public init(wrappedValue: Value) { self.mutex = Mutex(wrappedValue) } // 单线程/明确安全场景下可直接访问 public var wrappedValue: Value { get { mutex.withLock { $0 } } set { mutex.withLock { $0 = newValue } } } // 核心安全API:所有值操作在锁内完成,适配非Sendable类型 public func withLock<T>(_ operation: @sending (inout Value) throws -> T) rethrows -> T { try mutex.withLock(operation) } public var projectedValue: Atomic<Value> { self } }
使用方式
@Atomic private(set) var cameraTransform: Transform = .identity // 安全修改值(操作完全在锁内执行) cameraTransform.projectedValue.withLock { transform in transform.position.x += 10 transform.rotation.z = .pi / 2 } // 安全读取并处理 let currentPosition = cameraTransform.projectedValue.withLock { $0.position }
优势
- 保留属性包装器的简洁语法
- 通过闭包确保非Sendable值的操作始终在锁保护下,符合Mutex的安全约束
- 可选保留
wrappedValue直接访问,适配单线程场景
方案2:@unchecked Sendable标记(不推荐)
如果必须保留wrappedValue的直接get/set,可手动标记包装器为@unchecked Sendable,移除Value的Sendable约束:
@available(iOS 18.0, *) @propertyWrapper public final class Atomic<Value>: @unchecked Sendable { private let mutex: Mutex<Value> public init(wrappedValue: Value) { self.mutex = Mutex(wrappedValue) } public var wrappedValue: Value { get { mutex.withLock { $0 } } set { mutex.withLock { $0 = newValue } } } public var projectedValue: Atomic<Value> { self } }
注意事项
@unchecked Sendable意味着你手动担保类型的线程安全性,编译器不再做检查- 跨线程直接get/set非Sendable的
wrappedValue可能触发数据竞争,仅适合能确保访问安全的场景(如单线程读写)
原理说明
Mutex的withLock支持非Sendable值,是因为其闭包被标记为@sending,值的操作被限制在锁内,不会暴露到其他线程。而属性包装器的wrappedValue无法用sending修饰,直接返回/接收非Sendable值会触发编译器错误。闭包式API规避了这个问题,既符合安全约束,又保留了包装器的便利性。
内容的提问来源于stack exchange,提问作者HL666
相关产品推荐
相关产品推荐

