Swift Combine: для чего нужны эти функции многоадресной рассылки и как их использовать?

Борясь с некоторыми проблемами комбинирования, я наткнулся на раздел «Работа с несколькими подписчиками» в https://developer.apple.com/documentation/combine/publisher:

func multicast<S>(() -> S) -> Publishers.Multicast<Self, S>

func multicast<S>(subject: S) -> Publishers.Multicast<Self, S>

Однако, когда я попытался подтвердить свое предположение о том, что при отправке нескольким подписчикам потребуется многоадресная рассылка, я обнаружил, что в этом нет необходимости при попытке использовать этот код игровой площадки (измененный с https://github.com/AvdLee/CombineSwiftPlayground/blob/master/Combine.playground/Pages/Combining%20Publishers.xcplaygroundpage/Contents.swift) (запуск 10.14.5 в Xcode версии 11.0 beta 3 (11M362v)):

enum FormError: Error { }

let usernamePublisher = PassthroughSubject<String, FormError>()
let passwordPublisher = PassthroughSubject<String, FormError>()

let validatedCredentials = Publishers.CombineLatest(usernamePublisher, passwordPublisher)
    .map { (username, password) -> (String, String) in
        return (username, password)
    }
    .map { (username, password) -> Bool in
        !username.isEmpty && !password.isEmpty && password.count > 12
    }
    .eraseToAnyPublisher()

let firstSubscriber = validatedCredentials.sink { (valid) in
    print("First Subscriber: CombineLatest: Are the credentials valid: \(valid)")
}

let secondSubscriber = validatedCredentials.sink { (valid) in
    print("Second Subscriber: CombineLatest: Are the credentials valid: \(valid)")
}

// Nothing will be printed yet as `CombineLatest` requires both publishers to have send at least one value.
usernamePublisher.send("avanderlee")
passwordPublisher.send("weakpass")
passwordPublisher.send("verystrongpassword")

Это печатает:

First Subscriber: CombineLatest: Are the credentials valid: false
Second Subscriber: CombineLatest: Are the credentials valid: false
First Subscriber: CombineLatest: Are the credentials valid: true
Second Subscriber: CombineLatest: Are the credentials valid: true

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

Итак, для чего нужны эти функции многоадресной рассылки и как их использовать? Некоторые примеры кода были бы хороши.

Спасибо,

Ларс


person SqAR.org    schedule 06.07.2019    source источник


Ответы (2)


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

Но вот лучший тестовый пример (вдохновленный обсуждением Какао с любовью):

    let pub1 = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
    let sub = CurrentValueSubject<Int,Never>(0)
    let scan = sub.scan(10) {i,j in i+j}
    pub1.sink { _ in let i = sub.value; sub.value = i+1 }.store(in:&storage)
    scan.sink { print("a", $0) }.store(in:&storage)
    delay(3) {
        scan.sink { print("b", $0) }.store(in:&self.storage)
    }

Это дает явно странный результат, когда второй sink становится новым подписчиком этого конвейера:

a 10
a 11
a 13
a 16
b 13
a 20
b 17
a 25
b 22
a 31
b 28
a 38
b 35

Раковины a и b получают разные серии чисел друг от друга, потому что scan - это структура. Если мы хотим, чтобы они получали одинаковые числа, мы можем использовать многоадресную рассылку:

    let scan = sub.scan(10) {i,j in i+j}.multicast {PassthroughSubject()}.autoconnect()

Это дает

a 10
a 11
a 13
a 16
a 20
b 20
a 25
b 25

который является последовательным.

Однако это все равно не доказывает, что вам нужен multicast, потому что вы могли бы сделать то же самое, сказав вместо этого .share(). Я не понимаю, в чем разница между multicast и share.

person matt    schedule 17.12.2019
comment
Multicast () и share () почти одинаковы. Разница is shared () создает подписку, когда первая подписка подписывается, это приводит к тому, что вторая подписка может пропустить некоторые значения, если она подписана после того, как значение было отправлено. Multicast () может отложить подписку на восходящий поток, не вызывая autoconnect, как вы, вместо этого вызывая connect () после установки второй подписки. - person huync; 02.09.2020

Ответ / ссылка с форумов Swift подразумевает, что методы многоадресной рассылки созданы на основе оператора .share (). Из сообщения Филиппа:

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

На практике, если вы хотите разделить поток и многоадресные обновления событий через несколько конвейеров в Combine, кажется, что наиболее прагматичным способом является создание свойства @Published, когда любой восходящий конвейер обновляет его с помощью .assign () или внутри .sink ( ), а затем настройте дополнительные конвейеры с подписчиками из свойства @Published.

person heckj    schedule 18.07.2019