Перевод статьи подготовлен в преддверии старта продвинутого курса «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? Не стесняйтесь посещать мои другие статьи:
VashS
Изображения в 1-3 попали не в те блоки (