如何在没有自定义订阅者的情况下调整Combine的发布者需求?

时间:2019-11-26 16:08:46

标签: swift reactive-programming combine

Combine框架中,有一个demand的概念,它允许向publishers发出背压信号。

假设我有一个简单的发布者:

let numbers = Publishers.Sequence<ClosedRange<Int>, Error>(sequence: 0...100)

我想下载某些使用这些数字作为参数的URL。我还希望下一次下载仅在上一次下载完成后才能开始。

那么幼稚的方法看起来像这样:

let subscription = numbers.sink(receiveCompletion: { _ in }, receiveValue: {
  let url = URL(string: "https://httpbin.org/get?value=\($0)")!
  URLSession.shared.dataTask(with: url) { 
    $0.map { print(String(data: $0, encoding: .utf8)!) }
  }.resume()
})

不幸的是,这不能满足在开始下一个下载之前等待上一个下载完成的要求。据我所知,sink函数将返回类型为AnyCancellable的值,而不是类型为Subscription的值。如果是后者,我们可以在上传完成后根据特定需求在subscription上调用request函数。

控制sink或任何其他标准合并Subscriber提供的订阅需求的最佳方法是什么?

1 个答案:

答案 0 :(得分:2)

结果是,flatMap运算符采用了附加的maxPublishers参数,该参数采用了Subscribers.Demand的值。与Future发布者结合使用,这使得numbers发布者可以等到将来能够处理给定值,然后再发送下一个值。

将其应用于原始代码,一个接一个地下载值,如下所示:

enum DownloadError: Error {
    case noData
}

let subscription = numbers.flatMap(maxPublishers: .max(1)) { number in
    Future { promise in
        let url = URL(string: "https://httpbin.org/get?value=\(number)")!
        URLSession.shared.dataTask(with: url) {
            switch ($0, $2) {
            case let (data?, nil):
                promise(.success(data))
            case let (nil, error?):
                promise(.failure(error))
            default:
                promise(.failure(DownloadError.noData))
            }
        }.resume()
    }
}.sink(
    receiveCompletion: { _ in print("errors should be handled here") },
    receiveValue: { print(String(data: $0, encoding: .utf8)!) }
)