Как архивировать более 4 издателей - PullRequest
1 голос
/ 21 февраля 2020

Я использую Swift Combine для своих запросов API. Теперь я сталкиваюсь с ситуацией, когда мне нужно иметь более 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)

Ответы [ 4 ]

2 голосов
/ 22 февраля 2020

То, что мешает вам сжать произвольное количество издателей, - это очень прискорбный факт, что Apple решила сделать вывод операторов zip равным кортежу . Кортежи чрезвычайно негибки и ограничены в своих возможностях. Вы не можете иметь кортеж, скажем, из десяти элементов; и вы даже не можете добавить элемент в кортеж, потому что это заставляет вас получать другой тип. Поэтому нам нужен новый оператор, который выполняет ту же работу, что и zip, но выдает более мощный и гибкий результат, например массив.

И мы можем его создать! К счастью, сам оператор zip имеет параметр transform, который позволяет нам указать, какой тип вывода мы хотим.

Хорошо, так что, чтобы проиллюстрировать это, я заархивирую десять издателей все вместе. Во-первых, я сделаю массив из десяти издателей; они будут просто издателями, но этого достаточно, чтобы доказать, что, чтобы доказать, что я не обманываю, я добавлю произвольную задержку к каждому из них:

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

Хорошо, теперь я У нас есть множество издателей, и я соберу их вместе в al oop:

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> вместо кортежа. В отличие от кортежа, мне разрешено создавать массив любого размера, просто добавляя элементы каждый раз через l oop.

Хорошо, теперь result теперь является Zip-издателем, объединяющим десять издателей . Чтобы доказать это, я просто прикреплю к нему подписчика и напечатаю вывод:

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

Запускаем код. Наступает душераздирающая пауза - правильно, потому что каждый из этих издателей Just имеет различную случайную задержку, и правило zip состоит в том, что им all необходимо опубликовать sh, прежде чем мы получим какой-либо вывод. Все они делают, рано или поздно, и вывод появляется в консоли:

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

Точно правильный ответ! Я доказал, что на самом деле я объединил десять издателей, чтобы получить выходные данные, состоящие из одного вклада каждого из них.

Сжатие произвольного числа издателей задач с данными (или что вы используете) ничем не отличается.

(По вопросам, связанным с тем, как я сериализую произвольное число издателей задач с данными, см. Объединение платформы сериализации asyn c Операции .)

1 голос
/ 25 февраля 2020

(1) Предраг против (2) ответ Мэтта

(1) У меня есть проблема с запоминанием, как использовать результаты (обозначение в закрытии не в какой-то «легкой для запоминания» записи *) 1003 *

(2) Решение Мэтта ограничено тем же типом вывода, 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

Похоже, что это может быть решено компилятором, возможно, нам нужно попросить сообщество включить некоторые типы составных типов, которые будут доступны для нашего преимущества.

1 голос
/ 22 февраля 2020

Думаю, что вы можете сделать sh что-то вроде этого:

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)
0 голосов

Работает для меня, используя 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)
...