swift 如何压缩4个以上的出版商

yruzcnhs  于 2022-11-21  发布在  Swift
关注(0)|答案(9)|浏览(107)

我正在使用Swift合并来处理我的API请求。现在我面临着这样一种情况,我想将4个以上的并行请求压缩在一起。之前我使用Zip4()运算符将4个请求压缩在一起。我可以想象你在多个步骤中进行压缩,但我不知道如何为它编写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)
wmomyfyw

wmomyfyw1#

阻止你压缩任意数量的发布者的是一个非常不幸的事实,苹果公司选择把压缩操作符的输出变成一个 * 元组 *。元组是非常不灵活的,而且能力有限。你不可能有一个元组,比如说,10个元素;你甚至不能把一个元素附加到元组上,因为这会导致你得到一个不同的类型。因此,我们需要一个new运算符,它做的工作和zip一样,但是发出一些更强大和灵活的结果,比如数组。
幸运的是,zip操作符本身有一个transform参数,它允许我们指定想要的输出类型。
好的,为了说明,我将压缩 ten publishers。首先,我将创建一个由十个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的规则是,它们 * 都 * 需要在我们得到任何输出之前发布。它们迟早都会这样做,输出会出现在控制台中:

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

完全正确的答案!我已经证明了我确实把十个出版商压缩在一起,产生了由他们每个人的单一贡献组成的输出。
将任意数量的数据任务发布器(或您正在使用的任何发布器)压缩在一起也没有什么不同。
(For一个相关问题,我在其中学习了如何 * 序列化 * 任意数量的数据任务发布者,请参见Combine framework serialize async operations。)

lbsnaicq

lbsnaicq2#

你可以这样做:

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)
oyjwcjzk

oyjwcjzk3#

基于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"之后发射。

pod7payv

pod7payv4#

如果你想继续使用Zip语义,你可以为此编写一个自定义发布者。基本上,新的Zip5将是Zip4和第五个发布者之间的Zip

extension Publishers {
    struct Zip5<A: Publisher, B: Publisher, C: Publisher, D: Publisher, E: Publisher>: Publisher
    where A.Failure == B.Failure, A.Failure == C.Failure, A.Failure == D.Failure, A.Failure == E.Failure {
        typealias Output = (A.Output, B.Output, C.Output, D.Output, E.Output)
        typealias Failure = A.Failure
        
        private let a: A
        private let b: B
        private let c: C
        private let d: D
        private let e: E
        
        init(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E) {
            self.a = a
            self.b = b
            self.c = c
            self.d = d
            self.e = e
        }
        
        func receive<S>(subscriber: S) where S : Subscriber, Output == S.Input, Failure == S.Failure {
            Zip(Zip4(a, b, c, d), e)
                .map { ($0.0, $0.1, $0.2, $0.3, $1) }
                .receive(subscriber: subscriber)
        }
    }
}

extension Publisher {
    func zip<O1: Publisher, O2: Publisher, O3: Publisher, O4: Publisher>(_ o1: O1, _ o2: O2, _ o3: O3, _ o4: O4) -> Publishers.Zip5<Self, O1, O2, O3, O4> {
        .init(self, o1, o2, o3, o4)
    }
}

以类似的方式,Zip6Zip7可以写成:

  • Zip6 => Zip(Zip4(a, b, c, d, e), Zip(e, f))
  • Zip7 => Zip(Zip4(a, b, c, d, e), Zip3(e, f, g))

等等。
缺点是,这需要编写大量代码,如果你最终需要这种zip操作,也许这将是一个很好的机会来重新访问你的应用程序的设计,也许你根本不需要这么多的压缩。
为了支持上述内容,请看一下zip6()声明的样子:

func zip<O1: Publisher, O2: Publisher, O3: Publisher, O4: Publisher, O5: Publisher>(_ o1: O1, _ o2: O2, _ o3: O3, _ o4: O4, _ o5: O5) -> Publishers.Zip6<Self, O1, O2, O3, O4, O5> {
    .init(self, o1, o2, o3, o4, o5)
}

有如此多的泛型参数,以及所有泛型参数上的条件约束,会让使用和了解变得更困难。

a14dhokn

a14dhokn5#

通过使用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)
mbyulnm0

mbyulnm06#

(1)Predrag's与(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

这似乎可以通过编译器来解决,也许我们必须要求社区包含一些复合类型的扁平化,以便为我们提供优势。

cclgggtu

cclgggtu7#

我认为我需要这样的东西,但根据您的用例,您也可以使用collect()来等待所有上游出版商的完成,然后再做其他事情。

uhry853o

uhry853o8#

@马特的解决方案是要走的路!
我会使用简化版的reduce部分,基本上是从一个空的元素数组开始,然后在另一个版本的reduce函数中压缩10个justs中的每一个。
因此我不需要dropFirst(),在reduce闭包中我可以使用隐式return语句,注意zip闭包也更短,因为使用了swift语法糖。
下面我们来看看更新后的版本:

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

let start = Just([Int]()).eraseToAnyPublisher()

let result = justs.reduce(start) { publisher, element in
  publisher
    .zip(element) { $0 + [$1] }
    .eraseToAnyPublisher()
}
ryevplcw

ryevplcw9#

the article启发了一个解决方案,您可以按以下方式使用它:
第一个

相关问题