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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 02:03:09