在 iOS 并发编程中,os_unfair_lock 以其出色的性能,自 iOS 10 起成为替代已废弃 OSSpinLock 的重要底层同步原语。然而,随着 Swift 语言与并发模型的演进,开发者在直接使用这一底层 C API 时,往往会遇到一些与 Swift 值语义、存储地址稳定性以及 API 易用性相关的问题。
其中最核心的问题并不只是“值拷贝”本身,而是:
os_unfair_lock 这类底层锁依赖稳定的内存地址,而 Swift 的值语义与复制/传递机制并不天然适合直接承载这类按地址操作的同步原语。
os_unfair_lock 是一个 C 结构体,在 Swift 中表现为值类型。理论上,一旦它被复制,就会产生新的独立存储;此时,不同副本上的加锁操作彼此并不互斥,因为它们已经不是同一把锁。更广义地说,只要代码无法明确保证始终操作的是同一块底层锁存储,线程安全就可能被破坏。
这也是为什么直接在 Swift 中把 os_unfair_lock 当作一个普通值类型来传递、捕获或嵌入各种值语义结构里,往往并不可靠。
传统的规避方案通常有两类:
- 将锁封装在引用类型中,借助引用语义保证底层锁存储的稳定性;
- 手动堆分配底层锁对象,通过指针显式管理其生命周期。
例如,手动堆分配的做法虽然可行,但会引入额外的内存管理复杂度:
import osfinalclassManualLock{privatelet lockPtr: UnsafeMutablePointer<os_unfair_lock>init() { lockPtr = .allocate(capacity: 1) lockPtr.initialize(to: os_unfair_lock()) }funcwithLock<R>(_ body: ()throws -> R) rethrows -> R { os_unfair_lock_lock(lockPtr)defer { os_unfair_lock_unlock(lockPtr) }returntry body() }deinit { lockPtr.deinitialize(count: 1) lockPtr.deallocate() }}
这种方案的问题不在于不能用,而在于它过于底层:需要开发者自行保证生命周期、解锁时机与封装边界,既繁琐,也更容易出错。
OSAllocatedUnfairLock:更适合 Swift 的封装
为改善直接使用 os_unfair_lock 时的工程体验,Apple 在较新的系统版本中提供了 OSAllocatedUnfairLock。它的目标,是为 Swift 提供一个更适合现代语言语义的高性能互斥锁封装。
从使用者角度看,OSAllocatedUnfairLock 的价值主要体现在以下几点:
稳定的底层锁存储它避免了直接把底层锁当作普通 Swift 值到处传递所带来的身份与地址问题。开发者可以更安全地赋值、传参或闭包捕获,而不必自己处理底层锁存储的分配与管理。
自动生命周期管理无需像 UnsafeMutablePointer 方案那样手动申请和释放底层内存,降低了出错风险。
更适合 Swift 的 API 设计它提供了 withLock 这类基于作用域的接口,使“加锁—执行临界区—解锁”成为一个结构化操作,更符合 Swift 的错误处理与资源管理风格。
适合并发上下文使用它被设计为可在现代 Swift 并发环境中使用,相比直接操作底层 C 锁更自然。
简而言之,OSAllocatedUnfairLock 的意义不只是“替代一个旧 API”,而是把一个性能优秀但不够 Swift 友好的底层锁,包装成了更容易正确使用的现代接口。
基本用法
使用 withLock 管理临界区
OSAllocatedUnfairLock 最常见的使用方式是 withLock(_:)。它接收一个闭包,并保证闭包执行期间锁处于持有状态。
import osfinalclassDataCache{privatelet lock = OSAllocatedUnfairLock()privatevar storage: [String: Data] = [:]funcset(_ data: Data, for key: String) { lock.withLock { storage[key] = data } }funcget(_ key: String) -> Data? { lock.withLock { storage[key] } }}
这种基于闭包的 API 相比手动调用底层加解锁有几个明显优势:
- 错误路径更安全:即使闭包提前返回,或抛出错误,锁也能在作用域结束时被正确释放;
- 临界区边界清晰:加锁范围由闭包天然界定,代码可读性更好;
- 支持返回值:闭包可以直接返回结果,调用代码往往更简洁。
如果临界区中的代码可能抛出错误,withLock 也可以自然配合 throws 使用:
funcloadValue()throws -> Int {try lock.withLock {// 访问受保护状态return42 }}
尝试加锁
对于不希望阻塞当前执行路径的场景,可以使用 withLockIfAvailable(_:)。它只会在成功获取锁时执行闭包;如果当前锁不可用,则返回 nil。
iflet value = lock.withLockIfAvailable({ computeValueFromSharedState()}) { handleValue(value)} else { performFallbackTask()}
这种方式适合对时延敏感、或可以接受降级逻辑的场景。
总结
OSAllocatedUnfairLock 是 Apple 为现代 Swift 并发编程提供的重要改进之一。它解决的关键问题,不只是“少写几行样板代码”,而是把一个依赖稳定存储地址的底层锁原语,以更适合 Swift 的方式暴露出来。
它的价值主要在于:
- 避免直接使用
os_unfair_lock 时常见的值语义与存储地址问题; - 通过
withLock 等作用域化 API 提高正确性与可读性;
对于部署目标为 iOS 16 及以上、且确实需要使用互斥锁保护共享可变状态的项目,OSAllocatedUnfairLock 通常是比直接操作 os_unfair_lock 更推荐的选择。