Combine: 3 примера использования Publishers и Subscriber

На конференции WWDC 2019 был представлен новый фреймворк Combine. Всё последнее десятилетие среди программистов становились популярными декларативный и Event-based подходы к разработке приложений, т. к. они дают преимущество в скорости разработки приложений.

Combine предоставляет следующие возможности: 1. Асинхронное выполнение кода. 2. Составные компоненты. 3. Более простой мультитрединг. 4. Кроссплатформенный код.

Combine позволяет выполнять код «реактивно».

Publishers and Subscribers

Основой Combine является 2 концепции: 1. Subscriber выполняет роль Observer. 2. Publisher выполняет роль Observable.

Самый короткий пример их использования:

import Combine

let pub = Just("Hello")

_ = pub.sink {
    print($0)
}

Объявляем константу, где в структуру Just оборачивается строка "Hello", — это наш паблишер, который оповестит только раз. Ниже добавляем подписку с замыканием с помощью метода sink. И этот пример напечатает в итоге строку Hello.

@Published Property Wrapper

В Swift 5.1 появилась возможность использовать Property Wrappers, которые изначально назывались Property Delegates. Это очередная обёртка в виде структуры, позволяющая добавлять на чтение или запись свойства (property) дополнительное поведение. Combine же даёт возможность использовать их через @Published.

import Foundation
import Combine

class ObjectWithPublisher {
    @Published var someString: String = ""
}

var obj = ObjectWithPublisher()
obj.$someString.sink(receiveValue: { print ("\($0)") })

obj.someString = "Hello Combine!"

Обращаться к таким врапперам можно, используя $ перед свойством. Как и в прошлом примере, мы добавляем Subcriber с помощью метода sink, но свойство мы обновляем позже подписки. В итоге этот пример распечатает нам “Hello Combine!”

Subjects

В Combine такие сущности, как Subject также являются паблишерами, которым мы можем посылать события. Сейчас у нас есть 2 вида Subjects: 1. PassthroughSubject, с помощью которого вы получите все события, которые случились после вашей подписки. 2. CurrentValueSubject — получите всё тоже, что и с помощью PassthroughSubject, но также и предыдущее или инициализированное значение

import Combine

let subj = PassthroughSubject<String, Never>()
let pub = subj.eraseToAnyPublisher()

subj.send("Hello")

let subscriber = pub.sink(receiveValue: { print($0) })

subj.send("Combine")
subj.send("!")

Объявление Subjects требует от нас, чтобы кроме выходного типа мы также указали тип для ошибки, можем просто указать здесь Never. Функция eraseToAnyPublisher() делает нам Type Erasure для паблишера к типу AnyPublisher. Дальше уже знакомая нам подписка с помощью sink. События мы будет посылать с помощью метода send и получим лишь 2 последних отправления:

Combine
!
import Combine

let subj = CurrentValueSubject<String, Never>("Bonjour")
let pub = subj.eraseToAnyPublisher()

subj.send("Hello")

let subscriber = pub.sink(receiveValue: { print($0) })

subj.send("Combine")
subj.send("!")

Использование CurrentValueSubject отличается тем, что нам нужно указать начальное значение. И результат мы получим :

Hello
Combine
!

Если же мы не закомментируем первое отправление до подписки, то вместо Hello нам придёт начальная строка Bonjour.