Subscriber负责订阅Publisher,接收其发布的数据,只有订阅后才能接收到数据。
订阅方式
1、sink
sink是Combine中最常用、最直观的订阅方式,本质上就是一个“订阅者构造器”。
public func sink(receiveCompletion: @escaping (Subscribers.Completion<Self.Failure>) -> Void, receiveValue: @escaping (Self.Output) -> Void) -> AnyCancellable
sink同时接收receiveCompletion和receiveValue两个闭包。
1、receiveCompletion负责接收“流结束”:
enum Completion<Failure: Error> {
case finished
case failure(Failure)
}
当发布者抛出错误时,receiveCompletion会接收错误并结束,不再订阅。
2、receiveValue负责接收发布的值,Output在Publisher定义中表示发布的值。
3、AnyCancellable表示订阅关系,使用sink时需要被持有,如果没有人持有AnyCancellable,则会立即被销毁,订阅立即取消。
示例:
.sink(receiveCompletion: { completion in
// 订阅完成(正常完成或失败)时调用
switch completion {
case .finished:
print("正常完成")
case .failure(let failure):
print("失败")
}
}, receiveValue: { value in
// 每次收到 Publisher 发出的值时调用
print("value:\(value)")
})
当发布者(这里是somePublisher)发布新值后,sink 可以处理完成的状态以及接收发布的值。
4、在不关心receiveCompletion的情况下,还可以使用sink简写形式:
sink { value in ... }
只监听值,不处理结束或错误。
5、如果Publisher不再发布新值,可以使用 send(completion:_) 方法结束:
message.send(completion: .finished) // 正常结束
message.send(completion: .failure(Error)) // 抛出一个错误
receiveCompletion会接收到结束或错误,根据失败类型进行处理。
注意:只有支持失败的Publisher的类型(比如PassthroughSubject<String, Error>)才可以发送结束/抛出错误,否则无法使用send(completion:_)方法。
6、sink返回一个AnyCancellable类型,用于管理订阅,可以被取消。
2、assign
assign的作用为把Publisher发出的值,自动赋给某个对象的属性。
assign<Root: AnyObject>(
to keyPath: ReferenceWritableKeyPath<Root, Output>,
on object: Root
) -> AnyCancellable
Output为Publisher发出的值,keyPath为目标对象的可写属性,object为对象实例。
例如:
publisher.assign(to: \.text, on: label)
这表示将publisher发出的值,写入label对象的text属性。
注意:text属性需要为可写,并且为 @Publsihed 修饰。
在SwiftUI中,通常使用:
publisher.assign(to: &$viewModel.text) // 写入viewModel的text属性
publisher.assign(to: \.text, on: self) // 写入当前对象/视图的text属性
和sink是等价的:
publisher.sink { value in
self.text = value
}
因此,assign可以被理解为只能写属性的sink,通常用于数据绑定。
assign存在一些限制,比如只能用于不会失败的Publisher,因为assign没有地方处理失败情况。另外,也只能用于引用类型的对象。
3、onReceive
在SwiftUI中,推荐使用 onReceive() 代替 sink,因为onReceive可以自动管理生命周期(绑定视图),sink需要手动保留AnyCancellable,否则被释放后,不再接收事件。
let pastePublisher = PassthroughSubject<Void, Never>()
.onReceive(pastePublisher) {
// 每次调用 pastePublisher.send(),都会触发这里
}
onReceive订阅Combine发布者,当发布者发布时间时调用闭包。
详细内容请见《SwiftUI响应Combine的onReceive》。
订阅关系AnyCancellable
AnyCancellable是类型擦拭(Type Erasure)的产物,表示每种订阅都有不同的具体类型,Combine不关系具体细节,所以统一包装成 AnyCancellable。
当调用 sink 时,Combine会创建一条订阅管道,并返回“控制管道的权限”,这个权限就是AnyCancellable。
因为订阅不是一次函数调用,而是一个持续关系:
publisher.sink { ... }
发布者发布新值时,会通知订阅者,这段关系存在建立-维持-取消共三个阶段,AnyCancellable就是这个关系的实体对象。
错误示范:在onAppear中设置 sink:
.onAppear {
let Msg = message
.sink { item in
num = item
print("接受的值为:\(item)")
}
}
当发布者message发布新值时,并不会输出任何内容。
因为在onAppear中Msg是局部变量,当onAppear结束时,Msg被释放,订阅立即取消,Combine严格依赖ARC,不会自动保存订阅。
持有AnyCancellable的方式
AnyCancellable主要有三种持有方式:
1、手动持有
var cancellable: AnyCancellable?
cancellable = publisher.sink { ... }
2、批量管理
var cancellables = Set<AnyCancellable>()
publisher
.sink { ... }
.store(in: &cancellables)
Combine的推荐写法。
两种方式都适合在ViewController、ViewModel等长生命周期的对象。
3、SwiftUI自动持有
在SwiftUI中使用onReceive时:
.onReceive(message) { item in
print("接受的值为:\(item)")
}
SwiftUI会自动创建sink,保存AnyCancellable,并在视图消失时自动cancel,因此不需要手动持有AnyCancellable。
4、取消订阅
订阅后返回的对象,可以通过调用 .cancel() 停止订阅:
cancellable.cancel()
订阅取消后,Publisher仍然可以发送消息,但是没有人再监听,取消订阅可以避免内存泄漏。
总结
.sink创建订阅关系,通过创建AnyCancellable类型的变量持有订阅关系,最后可以使用 cancel() 取消订阅关系。
