Перевод статьи подготовлен в преддверии старта продвинутого курса «iOS-Разработчик».

В этой статье мы рассмотрим шесть полезных операторов объединения в Combine. Мы сделаем это на примерах, экспериментируя с каждым из них в Xcode Playground.
Исходный код доступен в конце статьи.
Ну что ж, без лишних разглагольствований, давайте приступим.
Эта группа операторов позволяет нам добавлять (prepend — дословно “добавить в начало”) к нашему исходному паблишеру события, значения или других паблишеров:
Результат:

Теперь давайте добавим другого издателя того же типа:
Результат аналогичен предыдущему (обратите внимание, что нам нужно отправить событие

Оператор
В результате мы видим

Аналогично тому, как ранее мы использовали

Более сложный оператор
Вот что происходит в коде:
В результате мы видим вывод

Для простоты, функция
Благодаря оператору

Мы используем
Результатом является чередующаяся последовательность элементов:

Оператор
Чтобы проиллюстрировать это, рассмотрим следующий реальный пример: у нас есть имя пользователя, пароль
После того, как

Оператор
У нас есть следующие соответствующие значения из
Последние значение

Исходный код доступен на Gist.
Вас интересуют другие типы операторов Combine? Не стесняйтесь посещать мои другие статьи:

В этой статье мы рассмотрим шесть полезных операторов объединения в Combine. Мы сделаем это на примерах, экспериментируя с каждым из них в Xcode Playground.
Исходный код доступен в конце статьи.
Ну что ж, без лишних разглагольствований, давайте приступим.
1. prepend
Эта группа операторов позволяет нам добавлять (prepend — дословно “добавить в начало”) к нашему исходному паблишеру события, значения или других паблишеров:
import Foundation import Combine var subscriptions = Set<AnyCancellable>() func prependOutputExample() { let stringPublisher = ["World!"].publisher stringPublisher .prepend("Hello") .sink(receiveValue: { print($0) }) .store(in: &subscriptions) }
Результат:
Hello и World! выводятся в последовательном порядке:
Теперь давайте добавим другого издателя того же типа:
func prependPublisherExample() { let subject = PassthroughSubject<String, Never>() let stringPublisher = ["Break things!"].publisher stringPublisher .prepend(subject) .sink(receiveValue: { print($0) }) .store(in: &subscriptions) subject.send("Run code") subject.send(completion: .finished) }
Результат аналогичен предыдущему (обратите внимание, что нам нужно отправить событие
.finished в subject, чтобы оператор .prepend работал):
2. append
Оператор
.append (дословно “добавить в конец”) работает аналогично .prepend, но в этом случае мы добавляем значения к исходному паблишеру:func appendOutputExample() { let stringPublisher = ["Hello"].publisher stringPublisher .append("World!") .sink(receiveValue: { print($0) }) .store(in: &subscriptions) }
В результате мы видим
Hello и World! выведенные на консоли:
Аналогично тому, как ранее мы использовали
.prepend для добавления другого Publisherа, у нас также есть такая возможность и для оператора .append:
3. switchToLatest
Более сложный оператор
.switchToLatest позволяет нам объединить серию паблишеров в один поток событий:func switchToLatestExample() { let stringSubject1 = PassthroughSubject<String, Never>() let stringSubject2 = PassthroughSubject<String, Never>() let stringSubject3 = PassthroughSubject<String, Never>() let subjects = PassthroughSubject<PassthroughSubject<String, Never>, Never>() subjects .switchToLatest() .sink(receiveValue: { print($0) }) .store(in: &subscriptions) subjects.send(stringSubject1) stringSubject1.send("A") subjects.send(stringSubject2) stringSubject1.send("B") // отброшено stringSubject2.send("C") stringSubject2.send("D") subjects.send(stringSubject3) stringSubject2.send("E") // отброшено stringSubject2.send("F") // отброшено stringSubject3.send("G") stringSubject3.send(completion: .finished) }
Вот что происходит в коде:
- Мы создаем три объекта
PassthroughSubject, которым мы будем отправлять значения. - Мы создаем главный объект
PassthroughSubject, который отправляет другие объектыPassthroughSubject. - Мы отправляем
stringSubject1на основной subject. stringSubject1получает значение A.- Мы отправляем
stringSubject2на основной subject, автоматически отбрасывая события stringSubject1. - Точно так же мы отправляем значения в
stringSubject2, подключаемся кstringSubject3и отправляем ему событие завершения.
В результате мы видим вывод
A, C, D и G:
Для простоты, функция
isAvailable возвращает случайное значение Bool после некоторой задержки.func switchToLatestExample2() { func isAvailable(query: String) -> Future<Bool, Never> { return Future { promise in DispatchQueue.main.asyncAfter(deadline: .now() + 2) { promise(.success(Bool.random())) } } } let searchSubject = PassthroughSubject<String, Never>() searchSubject .print("subject") .map { isAvailable(query: $0) } .print("search") .switchToLatest() .sink(receiveValue: { print($0) }) .store(in: &subscriptions) searchSubject.send("Query 1") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { searchSubject.send( "Query 2") } }
Благодаря оператору
.switchToLatest мы достигаем того, чего хотим. Только одно значение Bool будет выведено на экран:
4. merge(with:)
Мы используем
.merge(with:) для объединения двух Publishersов, как если бы мы получали значения только от одного:func mergeWithExample() { let stringSubject1 = PassthroughSubject<String, Never>() let stringSubject2 = PassthroughSubject<String, Never>() stringSubject1 .merge(with: stringSubject2) .sink(receiveValue: { print($0) }) .store(in: &subscriptions) stringSubject1.send("A") stringSubject2.send("B") stringSubject2.send("C") stringSubject1.send("D") }
Результатом является чередующаяся последовательность элементов:

5. combineLatest
Оператор
.combineLatest паблишит кортеж, содержащий последнее значение каждого издателя.Чтобы проиллюстрировать это, рассмотрим следующий реальный пример: у нас есть имя пользователя, пароль
UITextFields и кнопка продолжения. Мы хотим держать кнопку отключенной до тех пор, пока имя пользователя не будет содержать не менее пяти символов, а пароль — не менее восьми. Мы можем легко добиться этого, используя оператор .combineLatest:func combineLatestExample() { let usernameTextField = CurrentValueSubject<String, Never>("") let passwordTextField = CurrentValueSubject<String, Never>("") let isButtonEnabled = CurrentValueSubject<Bool, Never>(false) usernameTextField .combineLatest(passwordTextField) .handleEvents(receiveOutput: { (username, password) in print("Username: \(username), password: \(password)") let isSatisfied = username.count >= 5 && password.count >= 8 isButtonEnabled.send(isSatisfied) }) .sink(receiveValue: { _ in }) .store(in: &subscriptions) isButtonEnabled .sink { print("isButtonEnabled: \($0)") } .store(in: &subscriptions) usernameTextField.send("user") usernameTextField.send("user12") passwordTextField.send("12") passwordTextField.send("12345678") }
После того, как
usernameTextField и passwordTextField получат user12 и 12345678 соответственно, условие удовлетворяется, и кнопка активируется:
6. zip
Оператор
.zip доставляет пару соответствующих значений от каждого издателя. Допустим, мы хотим определить, паблишили ли оба паблишера одно и то же значение Int:func zipExample() { let intSubject1 = PassthroughSubject<Int, Never>() let intSubject2 = PassthroughSubject<Int, Never>() let foundIdenticalPairSubject = PassthroughSubject<Bool, Never>() intSubject1 .zip(intSubject2) .handleEvents(receiveOutput: { (value1, value2) in print("value1: \(value1), value2: \(value2)") let isIdentical = value1 == value2 foundIdenticalPairSubject.send(isIdentical) }) .sink(receiveValue: { _ in }) .store(in: &subscriptions) foundIdenticalPairSubject .sink(receiveValue: { print("is identical: \($0)") }) .store(in: &subscriptions) intSubject1.send(0) intSubject1.send(1) intSubject2.send(4) intSubject1.send(6) intSubject2.send(1) intSubject2.send(7) intSubject2.send(9) // Не отображено, потому что его пара еще не отправлена }
У нас есть следующие соответствующие значения из
intSubject1 и intSubject2:- 0 и 4
- 1 и 1
- 6 и 7
Последние значение
9 не выводится, поскольку intSubject1 еще не опубликовал соответствующее значение:
Ресурсы
Исходный код доступен на Gist.
Заключение
Вас интересуют другие типы операторов Combine? Не стесняйтесь посещать мои другие статьи:
