NSConditionLock 是对 NSCondition 的进一步封装。它最大的特点是:它不仅是一把锁,还自带一个“整数状态值(Condition Value)”。
在工程中,它最适合解决的是**“具有特定状态顺序依赖”**的并发任务。
1. 核心模型:基于状态的“条件准入”
普通的锁只有“开/关”两种状态。而 NSConditionLock 允许线程表达这样的意图:
“我不仅要拿到锁,我还要在 状态值为 X 的时候才拿到锁。”
它提供的 lock(whenCondition:) 方法会将加锁与条件检查原子化地结合在一起。
2. 实战场景 A:严格顺序的流水线任务
假设你有一个多步处理流程(如:解压 -> 解密 -> 渲染),这些任务分布在不同的并发队列中,但必须按顺序执行。
-
场景: * 线程 A 处理“解压”,完成后将状态设为
1。- 线程 B 等待状态
1,处理“解密”,完成后设为2。 - 线程 C 等待状态
2,处理“渲染”。
- 线程 B 等待状态
-
优势: 你不需要手动写
while循环和NSCondition的等待逻辑,NSConditionLock内部自动帮你处理了挂起与唤醒。
3. 实战场景 B:多生产者-单消费者的“批处理”
假设你正在编写一个日志上传器,只有当缓冲区填满(状态变为 Full)或者手动触发强制上传(状态变为 Force)时,消费线程才开始工作。
-
逻辑: * 消费线程调用
lock(whenCondition: BUFFER_FULL)。- 多个生产线程不断填充数据,直到某次写入发现缓冲区满了,调用
unlock(withCondition: BUFFER_FULL)。
- 多个生产线程不断填充数据,直到某次写入发现缓冲区满了,调用
-
优势: 相比信号量(Semaphore),它能更好地表达“满足某种特定业务逻辑状态”而非仅仅是“计数值”。
4. 实战场景 C:简单的读写控制或开关
虽然它不是专门的读写锁,但你可以用它来实现简单的**“模式切换”**。
-
状态 0: 待机模式。
-
状态 1: 工作模式。
-
状态 2: 维护/清理模式。
线程可以根据当前系统的整体状态,有选择地切入或挂起,而不需要在业务代码里写一堆
if/else。
5. NSConditionLock vs NSCondition
| 特性 | NSCondition | NSConditionLock |
|---|---|---|
| 逻辑复杂度 | 较高(需手动写 while 检查状态) | 较低(状态检查封装在 API 内部) |
| 核心变量 | 由开发者维护共享变量 | 内部维护一个 NSInteger |
| 性能 | 略快 | 略慢(内部有额外的状态管理开销) |
| 语义化 | 适合复杂的组合逻辑 | 适合单一的线性状态机 |
6. 工程建议
虽然 NSConditionLock 很方便,但在现代 iOS 开发中,它的出场率较低,主要原因如下:
-
粒度太粗: 它只能基于一个整数进行同步。如果你的逻辑涉及多个变量(比如“数组不为空”且“网络已连接”),它就显得捉襟见肘。
-
性能开销: 它是基于
NSCondition实现的,属于相对重量级的锁。 -
更优选择: * 简单的顺序依赖:使用
OperationQueue的addDependency:。- 复杂异步流:使用
Combine或 Swift 的Async/Await。
- 复杂异步流:使用
结论: 当你面临现成的、基于状态机的旧代码重构,或者需要一个非常直观的线性同步模型时,
NSConditionLock是最优雅的选择。