如何压缩4个以上的发布者

时间:2020-02-21 20:27:06

标签: swift combine

我正在对我的API请求使用Swift Combine。现在,我面临一种情况,我想将4个以上的并行请求压缩在一起。在我有4个使用Zip4()运算符压缩在一起的请求之前,我可以想象您要分多个步骤进行压缩,但是我不知道如何为它编写receiveValue。

这是我当前代码的简化,其中包含4个并行请求:

    Publishers.Zip4(request1, request2, request3, request4)
        .sink(receiveCompletion: { completion in
            // completion code if all 4 requests completed
        }, receiveValue: { request1Response, request2Response, request3Response, request4Response in
            // do something with request1Response
            // do something with request2Response
            // do something with request3Response
            // do something with request4Response
        }
    )
        .store(in: &state.subscriptions)

6 个答案:

答案 0 :(得分:10)

不幸的是,Apple选择将zip运算符的输出设为 tuple ,这使您无法压缩任意数量的发布者。元组非常不灵活,并且功能有限。您不能有一个由10个元素组成的元组;而且您甚至无法将元素附加到元组,因为这会导致您获得其他类型。因此,我们需要一个新的运算符,其作用与zip相同,但发出一些更强大,更灵活的结果,例如数组。

我们可以做一个!幸运的是,zip运算符本身具有一个transform参数,可让我们指定所需的输出类型。

好的,为了说明一下,我将把十个发布者压缩在一起。首先,我将组成十个发布者;他们将仅仅是Just Publishers,但这足以说明问题,并证明我没有作弊,我将对他们每个人施加任意延迟:

let justs = (1...10).map {
    Just($0)
        .delay(for: .seconds(Int.random(in:1...3)), scheduler: DispatchQueue.main)
        .eraseToAnyPublisher() }

好的,现在我有了一系列的发布者,我将它们循环压缩在一起:

let result = justs.dropFirst().reduce(into: AnyPublisher(justs[0].map{[$0]})) { 
    res, just in
    res = res.zip(just) {
        i1, i2 -> [Int] in
        return i1 + [i2]
    }.eraseToAnyPublisher()
}

请注意zip运算符后的结尾闭包!这样可以确保我的输出将是Array<Int>而不是元组。与元组不同,我可以创建任何大小的数组,每次循环都添加元素。

好的,所以result现在是一个Zip发布者,可以将十个发布者压缩在一起。为了证明这一点,我将为其附加一个订户并打印输出:

result.sink {print($0)}.store(in: &self.storage)

我们运行代码。有一个令人心碎的暂停-正确,因为每个Just出版商都有不同的随机延迟,并且zip规则是他们 all 都需要在我们获得任何输出之前进行发布。他们迟早都会这么做,并且输出出现在控制台中:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

正确的答案!我已经证明我实际上确实将十个发布者拉在一起,以产生由每个发布者的一个贡献组成的输出。

将任意数量的数据任务发布者(或您正在使用的任何东西)压缩在一起并没有什么不同。

(有关一个问题,我将学习如何序列化任意数量的数据任务发布者,请参阅Combine framework serialize async operations。)

答案 1 :(得分:2)

认为您可以这样实现:

let zipped1 = Publishers.Zip4(request1, request2, request3, request4)    
let zipped2 = Publishers.Zip4(request5, request6, request7, request8)

Publishers.Zip(zipped1, zipped2)
    .sink(receiveCompletion: { completion in
        // completion code if all 8 requests completed
    }, receiveValue: { response1, response2 in
        // do something with response1.0
        // do something with response1.1
        // do something with response1.2, response1.3, response2.0, response2.1, response2.2, response2.3
    }
)
    .store(in: &state.subscriptions)

答案 2 :(得分:2)

基于Matt's answer

extension Publishers {
    struct ZipMany<Element, F: Error>: Publisher {
        typealias Output = [Element]
        typealias Failure = F

        private let upstreams: [AnyPublisher<Element, F>]

        init(_ upstreams: [AnyPublisher<Element, F>]) {
            self.upstreams = upstreams
        }

        func receive<S: Subscriber>(subscriber: S) where Self.Failure == S.Failure, Self.Output == S.Input {
            let initial = Just<[Element]>([])
                .setFailureType(to: F.self)
                .eraseToAnyPublisher()

            let zipped = upstreams.reduce(into: initial) { result, upstream in
                result = result.zip(upstream) { elements, element in
                    elements + [element]
                }
                .eraseToAnyPublisher()
            }

            zipped.subscribe(subscriber)
        }
    }
}

单元测试可以使用以下内容作为输入:

let upstreams: [AnyPublisher<String, Never>] = [
    Just("first")
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher(),
    Just("second").eraseToAnyPublisher()
]

.receive(on:)将该事件的发出放在主队列的末尾,这样它将在"second"之后发出。

答案 3 :(得分:1)

(1)Predrag的vs(2)Matt的答案

(1)我很难记住如何使用结果(闭包中的命名不是用“容易记住”的符号表示的

(2)Matt的解决方案仅限于相同的输出类型,zip没有此限制

我建议使用其他变体

let handler =
    publisher1
        .zip(publisher2)
        .zip(publisher3)
        .zip(publisher4)
        .zip(publisher5)
        .zip(publisher6)

        .sink(receiveCompletion: { (c) in
            print(c)
        }) { (value) in
            print(
                value.1,            // 1
                value.0.1,          // 2
                value.0.0.1,        // 3
                value.0.0.0.1,      // 4
                value.0.0.0.0.1,    // 5
                value.0.0.0.0.0     // 6
            )
}

要达到最佳效果还很遥远,但是(至少对我来说)易于使用和压缩发布者的数量实际上是无限的

快速语法

GRAMMAR OF A TUPLE TYPE

tuple-type → ( ) | ( tuple-type-element , tuple-type-element-list )
tuple-type-element-list → tuple-type-element | tuple-type-element , tuple-type-element-list
tuple-type-element → element-name type-annotation | type
element-name → identifier

看来,这可以由编译器解决,也许我们必须要求社区包括一些平整的化合物类型,以便对我们有利。

答案 4 :(得分:1)

使用 transform

为我工作
let pub1: Just<Int> = Just(1)
let pub2: Just<String> = Just("string")
let pub3: Just<Double> = Just(1)
let pub4: Just<Float> = Just(1)

let pub = pub1
    .zip(pub2)
    .zip(pub3, { return ($0.0, $0.1, $1) })
    .zip(pub4, { return ($0.0, $0.1, $0.2, $1) })

var cancel: Set<AnyCancellable> = .init()

pub.sink {
    print($0.0) // is Int
    print($0.1) // is String
    print($0.2) // is Double
    print($0.3) // is Float
}.store(in: &cancel)

或使用 Publishers.Zip4

的示例
let pub1: Just<Int> = Just(1)
let pub2: Just<String> = Just("string")
let pub3: Just<Double> = Just(1)
let pub4: Just<Float> = Just(1)

let pub5: Just<Int> = Just(2)
let pub6: Just<String> = Just("string2")
let pub7: Just<Double> = Just(2)
let pub8: Just<Float> = Just(2)

let zip1 = Publishers.Zip4(pub1, pub2, pub3, pub4)
let zip2 = Publishers.Zip4(pub5, pub6, pub7, pub8)

let pub = zip1.zip(zip2, { return ($0.0 ,$0.1, $0.2, $0.3, $1.0, $1.1, $1.2, $1.3) })

var cancel: Set<AnyCancellable> = .init()

pub.sink {
    print($0.0) // is Int
    print($0.1) // is String
    print($0.2) // is Double
    print($0.3) // is Float
    print($0.4) // is Int
    print($0.5) // is String
    print($0.6) // is Double
    print($0.7) // is Float
}.store(in: &cancel)

答案 5 :(得分:0)

我以为我需要这样的东西,但是根据您的用例,您还可以使用collect()等待所有上游发布者的完成,然后再执行其他操作。

相关问题