咱们在运用订阅者的时分,都是用Sink或许Assign,可是咱们有没有想过一个问题,这两种订阅者在第一次衔接到发布者的时分,会发送一个无限大(unlimited)的需求(Demand)。

这个时分,订阅者就会一直不断的接纳到发布者发过来的内容,按理说,订阅者无条件接纳就能够了,可是,假如发布者发布的速度太快了,而订阅者接纳的速度很慢,接纳不了,那怎么办呢?又或许说,我不需要实时接纳,我只需要隔一段时刻接纳一次,这种需求也是十分多见的。

这个时分就涉及到一个概念,背压(back pressure),或许叫回压,咱们能够经过这个背压,来精确的操控发布者什么时分生成元素,咱们一般了解的话,发布者应该是自动发布的,然后订阅者被迫的去接纳。

其实不是,而是由订阅者去衔接和获取元素的时分,才进行发布,这个时分,咱们就能够经过运用Subscribers.Demand这个类型来告知发布者我能够接纳多少个元素,也便是回来能够追加接纳的事情数量,这样就能够做到操控发布者的发送速度,以此来定义 Backpressure 的响应行为。

Combine 在规划思路和 API 等等很多地方都参阅了 ReactiveX,特别是详细到 RxSwift 的完成。假如你对响应式编程有了必定的认识的话,把你的项目从 RxSwift 迁移到 Combine 应该是十分容易的,不得不说Combine“抄袭”的十分成功。

假如非要说 RxSwift 和 Combine 的最大的不同之处,那便是 RxSwift 到现在为止都没有支持 backpressure,只有RxJava才有这个机制;可是 Combine 中原生对这个特性进行了支持。

我写了一个demo,发布者是这个定时器:

Combine之Backpressure

点击button的时分,就开端订阅:

Combine之Backpressure

这个订阅者是自定义的,他遵从Subscriber协议,然后完成协议里边的三个办法:

Combine之Backpressure

第一个办法里边,运用接纳到的这个订阅subscription,去向发布者恳求元素,这个Subscription协议便是衔接发布者和订阅者的桥梁;第二个办法是用来告知订阅者,发布者已经发生了元素。

并且能够接纳到一个Date元素input,然后回来一个需求量,也便是你期望订阅者还能够接纳多少个元素;第三个办法告知订阅者,发布者已经发布完了,不管是发布正常或许是有错误,这个结果我都会告知你。

说的再简略点,发布者会跟踪一切的订阅者,看谁的需求没有满意,就发生元素给谁,一直到满意一切的需求,发布者就不发生元素了,任务就完成了,在第一个办法里边,发布者和订阅者就都存在了。

可是需求为0,就不会发生任何元素,一直到1秒钟延时完毕执行到闭包里边的request,订阅者就给了发布者一个非零的需求,现在发布者就开端发布元素,并且是每隔一秒发布一次,总共发布三个元素就会中止发布,可是也并不会执行第三个办法打印完成,由于发布者还在等待更多的需求。

所以这时分假如有需要的话,订阅者能够把这个订阅次数保存下来,然后定时去恳求元素,就能够很灵敏的办理这个发布进程。比方有一个十分常见的开发场景,咱们能够在输入框中输入一些内容进行查找操作,并且一旦输入框的内容改变了,我就去调用接口改写对应的列表数据,但这个接口调用频率是必定要进行操控的,不然的话。

假如我按住一个英文字母键不放开,输入框会一直在变化,就会不断的去调用接口来改写页面数据,就算你的代码逻辑很好,不会卡顿不会溃散,你们的后台人员也肯定会骂你,由于无缘无故增加了服务器压力,这个时分,就能够用到这个背压的方式来进行操控和处理。

并且还有更简略的方式,便是直接运用背压操作符,完全不需要自定义订阅者:

1.buffer(size:prefetch:whenFull:),保存来自上游发布者的固定数量的项目。缓冲满了之后,缓冲区会丢弃元素或抛出错误;

2.debounce(for:scheduler:options:),只在上游发布者在指定的时刻距离内中止发布时才发布;

3.throttle(for:scheduler:latest:),以给定的最大速率生成元素。假如在一个距离内接纳到多个元素,则仅发送最新的或最早的元素;

4.collect(:) 和 collect(:options:) 集合元素,直到它们超过给定的数量或时刻距离,然后向订阅者发送元素数组。假如订阅者能够同时处理多个元素,这个操作符将是很好的选择。

这些操作符都能够操控订阅者接纳的元素数量,所以能够放心肠衔接无限需求的订阅者,比方: sink(receiveValue:) 和 assign(to:on:)。

Debounce是防抖的意思,Throttle是节流,他们俩在前端开发中或许会经常用到,做iOS开发或许很多人都不知道这个概念,其实咱们在工作中或多或少都遇到过需要运用背压的场景,只是大多数人接触的不多,没有详细了解到概念和原理的对应联系,就像规划形式有很多种,实际开发中咱们用到了某种规划形式自己却不知道。

实际运用我就不写了,比较简略,相似这样:

Combine之Backpressure

别的,咱们也能够经过设置 flatMap 的 maxPublishers来操控发布频率,我举个比如:

Combine之Backpressure

然后,在点击事情里边进行调用:

Combine之Backpressure

这样也同样完成了每隔一秒发布一次,可是必定要注意一个问题,假如我把publisher更换成 PassthoughSubject 或 Notification,就会呈现数据遗漏的情况。由于咱们限制了数据的并行处理数量,所以就导致数据的消耗时刻超过了数据的生成时刻。这个时分,咱们就需要在 Publisher 的后面添加 buffer 来对数据进行缓冲:

Combine之Backpressure

最终,把Publisher转换成AsyncSequence也能够做到相似的作用,创建一个遵从AsyncSequence协议的结构体,将从 Publihser 中获取的数据经过 AsyncStream 转送出去,并将迭代器指向 AsyncStream 的迭代器即可,这儿就不展开来写了,学无止境,执笔共勉。

更多干货内容尽在“逻辑iOS技术号”