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 Version 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

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

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

Спасибо,

Lars

1 ответ

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.

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

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

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

Другие вопросы по тегам