Комбинировать: публиковать элементы последовательности с некоторой задержкой

Я новичок в Combine и хотел бы получить, казалось бы, простую вещь. Скажем, у меня есть набор целых чисел, например:

let myCollection = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Я хочу публиковать каждый элемент с задержкой, например, 0,5 секунды.

print 0
wait for 0.5secs
print 1
wait for 0.5secs
and so forth

Я могу легко получить издателя последовательности и распечатать элементы следующим образом:

let publisherCanc = myCollection.publisher.sink { value in
    print(value)
}

Но в этом случае все значения печатаются сразу. Как я могу распечатать значения с задержкой? В Combine есть модификатор .delay, но он не для того, что мне нужно (действительно, .delay задерживает весь поток, а не отдельные элементы). Если я попробую:

let publisherCanc = myCollection.publisher.delay(for: .seconds(0.5), scheduler: RunLoop.main).sink { value in
    print(value)
}

Все, что я получаю, это просто "начальная" задержка, после чего элементы сразу печатаются.

Спасибо за вашу помощь.


person matteopuc    schedule 14.04.2020    source источник
comment
Я не знаком с Combine, но вот решение в RxSwift. stackoverflow.com/a/47585896/3141234 Просто нужно найти соответствующие имена методов в Combine, но идея проста. Сделайте наблюдаемый объект, который генерирует события с заданным интервалом, заархивируйте это наблюдаемое с помощью наблюдаемого объекта вашего массива.   -  person Alexander    schedule 14.04.2020
comment
Кстати, я предлагаю освоиться с RxSwift, возможно, даже с ReactiveCocoa. Это гораздо более зрелые / популярные фреймворки, поэтому в них гораздо больше документации и существующего кода. Часто между их функциями и функциями Combine существует соответствие 1: 1, только с разными именами.   -  person Alexander    schedule 14.04.2020
comment
Буду, спасибо за вашу помощь!   -  person matteopuc    schedule 14.04.2020


Ответы (3)


Используя идею из ответа, связанного с Александр в комментариях, вы можете создать издателя, который будет генерировать значение каждые 0,5 секунды, используя Timer.publish(every:on:in:), затем zip, который вместе с вашим Array.publisher, чтобы ваш нисходящий издатель выдавал значение каждый раз, когда оба ваших издателя выпустили новое значение.

Publishers.Zip берет n-й элемент своего вышестоящего издателя и излучает только тогда, когда оба его восходящего потока достигают n переданных значений - следовательно, путем объединения издателя, который передает свои значения только с 0.5 second интервалами, с вашим исходным издателем, который излучает все свои значения немедленно, вы задерживаете каждое значение на 0,5 секунды.

let delayPublisher = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
let delayedValuesPublisher = Publishers.Zip(myCollection.publisher, delayPublisher)
let subscription = delayedValuesPublisher.sink { print($0.0) }
person Dávid Pásztor    schedule 14.04.2020

Попробуйте использовать flatMap (maxPublishers :) с задержкой (для операторов: scheduler :).

import Foundation
import Combine

var tokens: Set<AnyCancellable> = []

let valuesToPublish = [1, 2, 3, 4, 5, 6, 7, 8, 9]
valuesToPublish.publisher
    .flatMap(maxPublishers: .max(1)) { Just($0).delay(for: 1, scheduler: RunLoop.main) }
    .sink { completion in
        print("--- completion \(completion) ---")
    } receiveValue: { value in
        print("--- value \(value) ---")
    }
    .store(in: &tokens)

Установив свойство maxPublishers, вы можете указать максимальное количество одновременных подписок издателей. Apple

person Ace Rodstin    schedule 21.03.2021

Основываясь на примерах, приведенных в других ответах, я придумал решение с дженериками:

import Combine
import SwiftUI

struct TimedSequence<T: Any>  {
    typealias TimedJointPublisher = (Publishers.Zip<Publishers.Sequence<[T], Never>, Publishers.Autoconnect<Timer.TimerPublisher>>)
    
    var sink: AnyCancellable?

    init(array: [T], interval: TimeInterval, closure: @escaping (T) -> Void) {
        let delayPublisher = Timer.publish(every: interval, on: .main, in: .default).autoconnect()
        let timedJointPublisher = Publishers.Zip(array.publisher, delayPublisher)
        self.sink = timedJointPublisher.sink(receiveValue: {r in
            closure(r.0)
        })
    }
}

Использование:

1 - Основные типы:

let m = TimedSequence(array: [1, 2, 3], interval: 2, closure: {
    element in
    let textReceived = String(element)        //assigns 1 ...2 seconds...then 2...2 seconds...then 3 to textReceived
})

let m = TimedSequence(array: ["Hello", "World"], interval: 2, closure: {
    element in
    let textReceived = element.upperCased()   //assigns HELLO ...2 seconds... then WORLD to textReceived

2 - Пользовательские типы:

class MyClass {
    var desc: String
    init(desc: String) {
        self.desc = desc
    }
}


let m = TimedSequence(array: [MyClass(desc: "t"), MyClass(desc: "s")], interval: 2, closure: {
    str in
    let textReceived = str.desc.uppercased()
})

То же, что и 1, за исключением того, что str.desc (S и T соответственно) назначается textReceived с интервалом в 2 секунды.

person Nirav Bhatt    schedule 25.05.2021