[Разработка под iOS, Swift] 6 объединяющих операторов Swift Combine, которые вам следует знать (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Перевод статьи подготовлен в преддверии старта продвинутого курса «iOS-Разработчик».
В этой статье мы рассмотрим шесть полезных операторов объединения в 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? Не стесняйтесь посещать мои другие статьи:
- 5 трансформирующих операторов Combine, которые вам следует знать
- 9 фильтрующих операторов Combine, которые вам следует знать
===========
Источник:
habr.com
===========
===========
Автор оригинала: Zafar Ivaev
===========Похожие новости:
- [IT-инфраструктура, Open source, Python] Как я делал Telegram-бота для работы с сетью
- [Браузеры, Монетизация веб-сервисов, IT-компании] В iOS 14 Apple News+ перехватывает ссылки на новостные сайты и забирает трафик себе
- [Разработка под iOS, Swift] Пользовательский инструмент, который не помешал бы вам в вашем приложении (перевод)
- [Информационная безопасность, Разработка под iOS, Разработка мобильных приложений, Монетизация мобильных приложений] ФАС обвинила Apple в ограничении конкуренции на рынке iOS-приложений
- [Разработка под iOS, Objective C, Swift] Разделяй и властвуй. Модульное приложение из монолита на Objective-C и Swift
- [Программирование, Управление разработкой, Управление проектами, Управление продуктом, DevOps] Типовые ситуации при непрерывной интеграции (перевод)
- [Программирование, .NET, C#] Что же такого особенного в IAsyncEnumerable в .NET Core 3.0? (перевод)
- [Программирование, Go] Новый API Go для Protocol Buffers (перевод)
- [Разработка под iOS, Смартфоны, IT-компании] Apple подтвердила, что облачные сервисы по типу xCloud и Stadia нарушают условия AppStore и поэтому запрещены
- [Программирование, Java] Интеграционное тестирование в SpringBoot с TestContainers-стартером (перевод)
Теги для поиска: #_razrabotka_pod_ios (Разработка под iOS), #_swift, #_programming, #_swift, #_ios, #_mobile, #_xcode, #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
), #_razrabotka_pod_ios (
Разработка под iOS
), #_swift
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 17:18
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Перевод статьи подготовлен в преддверии старта продвинутого курса «iOS-Разработчик». В этой статье мы рассмотрим шесть полезных операторов объединения в 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) } Вот что происходит в коде:
В результате мы видим вывод 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:
Последние значение 9 не выводится, поскольку intSubject1 еще не опубликовал соответствующее значение: Ресурсы Исходный код доступен на Gist. Заключение Вас интересуют другие типы операторов Combine? Не стесняйтесь посещать мои другие статьи:
=========== Источник: habr.com =========== =========== Автор оригинала: Zafar Ivaev ===========Похожие новости:
Блог компании OTUS. Онлайн-образование ), #_razrabotka_pod_ios ( Разработка под iOS ), #_swift |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 17:18
Часовой пояс: UTC + 5