我正在对我的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)
答案 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)
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()
等待所有上游发布者的完成,然后再执行其他操作。