我正在参加「掘金·启航计划」
sink
sink
负责订阅Publisher,并返回一个AnyCancellabel
。
完整签名:
public func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
它接受两个闭包回调做为参数,receiveCompletion
在发布结束事件时被调用,receiveValue
在发布值时被调用。
来自喵神《SwiftUI和Combine编程》中的发布-订阅流程图:
在使用 sink
完成订阅时,会创建一个特殊的 Subscriber 类型 Subscribers.Sink,并被纳入上面的流程中。它会自动声明想要接收无限多个新值,并订阅相应的 Publisher 对象。接下来,Publisher 会将新的值和结束事件作为参数传递给 sink
传入的两个闭包,从而将响应式的事件流转化为普通的指令操作。
Backpressure(背压)
Subscriber 可以在订阅初期通过 Subscription.request 或者通过 Subscriber.receive 返回特定的 Subscribers.Demand 值来指定能够处理的值的个数。这种背压机制 (Backpressure),可以让我们指定合适的背压策略,来控制可接收的值的上限,防止出现上游的发布速度超过下游消费速度的问题。
assign
Combine内建的另一个Subscriber就是assign
,它可以用来将 Publisher 的输出值通过 key path 绑定到一个对象的属性上去。
使用assign
时有几点需要注意的地方:
-
只有 class 上用 var 声明的属性才可以通过 assign 来直接赋值
-
上游 Publisher 的 Failure 的类型必须是 Never。如果上游 Publisher 可能会发生错误,必须先对它进行处理,比如使用
replaceError
或者catch
来把错误在绑定之前就处理掉。 -
在核心概念 中已经有过说明,使用
assign(to:on:)
并存储生成的 AnyCancellable,可能会引起引用循环,所以请尽量使用assign(to:)
来替代assign(to:on:)
引用共享
一个Publisher可能会有多个Subscriber,如果这个Publisher是一个网络请求的话,由于 dataTaskPublisher
是Struct,遵循值语义,多次订阅会复制多份,每一份都是一个新的Publisher,而这会造成多次请求,这是很浪费资源的。
解决上面问题的方法就是使用share()
来共享Publisher。share()
操作会把原来的Publisher包装到class内,对它的进一步变形也会适用于引用语义。
Cancellable & AnyCancellable
使用sink
或者assign
订阅Publisher时,会返回一个类型为AnyCancellable
类型的值;而Timer在执行connect()
操作后得到的是一个遵循Cancellable
协议的值。
对于 Cancellable 来说,需要在合适的时候主动调用 cancel()
方法来完结。如果在没有调用 cancel()
的情况下就将 connect
的返回值忽略或者释放掉,那么Timer会一直计时,永远不会被终结掉。所以对于需要connect
的Publisher,需要显式的调用cancel()
来结束事件。
AnyCancellable 是一个 class,它可以对自身的生命周期进行管理。在 AnyCancellable 被释放时,它对应的订阅操作也会停止。
在应用中,我们会在实例当中创建一个Set<AnyCancellable>
存储属性,并将sink
或者assign
返回的AnyCancellable存储其中。这样,当该实例 deinit
时,AnyCancellable 的 deinit
也会触发,并自动释放资源。这跟 RxSwift 中的 DisposeBag
很类似。
参考
王巍 《SwiftUI和Combine编程》