在 Combine 中,Cancellable(及其最常见的实现 AnyCancellable)扮演的是**“资源租约”和“生命周期控制器”**的角色。
理解它的工作原理可以从以下四个层面拆解:
1. 本质:一种“清理”协议
Cancellable 本身是一个非常简单的协议,只定义了一个方法:
Swift
protocol Cancellable {
func cancel()
}
它的核心职责是手动切断订阅者(Subscriber)与发布者(Publisher)之间的连接。一旦调用 cancel(),发布者将停止发送数据,且该订阅链条占用的所有内存资源都会被释放。
2. 工作原理:RAII 模式
AnyCancellable 是该协议的类型擦除实现,它利用了 Swift 的 RAII(Resource Acquisition Is Initialization,资源获取即初始化) 机制:
- 初始化(订阅开始) :当你调用
.sink或.assign时,系统会创建一个Subscription对象,并将其包装在AnyCancellable中返回给你。 - 持有(生命周期绑定) :只要你持有着这个
AnyCancellable变量(通常存在Set<AnyCancellable>中),订阅就有效。 - 销毁(自动取消) :这是最重要的特性。当
AnyCancellable变量被销毁(例如它所属的 ViewModel 被释放,或者包含它的 Set 被清空)时,它的deinit方法会自动被调用。在deinit内部,它会自动执行cancel()。
3. 为什么需要 store(in: &cancellables)?
很多初学者会疑惑:为什么不能直接写 .sink { ... } 而非要存起来?
- 如果不存储:
.sink返回的AnyCancellable是一个局部变量。函数执行结束,该变量立即被销毁,触发deinit,导致订阅在还没来得及收到任何数据时就瞬间关闭了。 - 存储在 Set 中:通过将它存入
Set,你将订阅的生命周期交给了这个Set。只要这个Set(通常是类的一个属性)还活着,订阅就一直活着。
4. 内部链路:Cancel 是如何传递的?
当你调用 cancel() 时,信号会逆流而上:
- 用户销毁
AnyCancellable或手动调用cancel()。 - AnyCancellable 调用内部
Subscription的cancel()。 - Subscription 收到指令,停止向上游 Publisher 请求数据(Backpressure 停止)。
- Publisher 收到取消信号,释放对闭包、网络请求或定时器的引用。
5. 防御式编程技巧
-
防止重复订阅: 如果你在方法里多次调用
.store(in:)往同一个 Set 里存,会产生多个活跃订阅。如果你的意图是“每次只保留最新的订阅”,你应该将AnyCancellable存为一个可选属性:Swift
var searchTask: AnyCancellable? func search(text: String) { // 赋值新任务时,旧任务会自动销毁并 cancel() searchTask = api.fetch(text).sink(...) } -
手动清理: 如果你想提前停止所有任务,只需调用
cancellables.removeAll()。
总结
Cancellable 是 Combine 内存安全的基础。它通过 Swift 的引用计数机制(ARC)确保了:当一个对象(如 View/ViewModel)消失时,它发起的异步任务(网络、定时器等)必须随之消失,从而彻底杜绝内存泄漏和野指针回调。