Функциональное реактивное программирование
surf_mobileНаверняка каждый iOS-разработчик, просматривая вакансии, не раз сталкивался с требованием: «Необходимо знание RxSwift и RxCocoa».
Эти фреймворки основаны на концепции реактивного программирования — подхода, который строится вокруг реакций на события. Например, пользователь взаимодействует с интерфейсом и ожидает мгновенного отклика от приложения. Такой стиль программирования популярен в мире фронтенда, включая iOS.
Ранее использование реактивного программирования в iOS-приложениях имело свои недостатки:
- Лишние зависимости — сторонние библиотеки увеличивали сложность проекта.
- Сложность поддержки — отладка и поддержка кода часто превращались в непростую задачу.
Но с появлением Combine и SwiftUI у нас больше нет необходимости прибегать к сторонним решениям. Теперь можно строить реактивные приложения, используя нативные инструменты Apple. Мы уже применяем подход и видим его преимущества.
Главные элементы Combine
Чтобы эффективно работать с Combine, важно разобраться в принципах реактивного программирования.
В основе Combine лежат четыре ключевых элемента:
- Publisher — источник данных, который отправляет значения.
- Subscriber — подписчик, который получает и обрабатывает данные.
- Operators — операторы, позволяющие трансформировать потоки данных.
- Subjects — особые сущности, объединяющие свойства Publisher и Subscriber.
Publisher (издатель)
В Combine ключевую роль играет Publisher — это протокол, который обозначает, что тип может передавать последовательность значений с течением времени.
Когда у Publisher есть подписчик (Subscriber), он отправляет ему данные по мере их появления. Но если подписчиков нет, Publisher остаётся неактивным и ничего не передаёт.
Publisher описывается двумя параметрами:
- Output — тип данных, которые он отправляет.
- Failure — тип ошибки, которая может возникнуть. Если ошибок не предполагается, используется Never.
Subscriber (подписчик)
Subscriber отвечает за получение данных от Publisher, а также за обработку возможных ошибок.
Как и Publisher, подписчик описывается двумя типами:
- Input — тип данных, которые он принимает.
- Failure — тип возможной ошибки.
Основная роль Subscriber — запрашивать данные и контролировать их объём при получении. Именно подписчик инициирует процесс передачи данных в реактивной цепочке.
В Combine у подписчика есть два способа обработать входящие данные:
sink(receiveCompletion:receiveValue:)
Метод принимает два замыкания:
- Первое вызывается, когда передача данных завершена. В него передаётся Subscribers.Completion, который указывает, завершил ли Publisher работу успешно или вернул ошибку.
- Второе выполняется при получении новых данных от Publisher.
assign(to:on:)
Этот метод немедленно присваивает каждое полученное значение указанному свойству объекта. Использует keyPath для указания свойства.
Operators (операторы)
Операторы — это промежуточное звено между Publisher и Subscriber. Они позволяют обрабатывать, преобразовывать и фильтровать данные в потоке.
С помощью операторов можно:
- Изменять значения, прежде чем передать их подписчику.
- Фильтровать ненужные данные.
- Комбинировать несколько потоков.
- Управлять временем доставки данных.
Операторы позволяют строить гибкие цепочки обработки, делая код более читаемым и предсказуемым. Давай разберёмся, какие они бывают.
Subjects
Subjects — это особый вид Publisher, который позволяет вручную отправлять данные подписчикам с помощью метода .send(_:).
Для чего используются Subjects:
- Позволяют вводить значения в поток данных вручную.
- Часто применяются для интеграции императивного кода в реактивную цепочку.
Как это работает:
- Publisher передаёт данные, пока не завершится или не возникнет ошибка.
- Если подписка больше не нужна, её можно отменить.
Как отменить подписку:
Все Subscriber реализуют протокол Cancellable. Он предоставляет метод .cancel(), который завершает подписку и останавливает получение данных.
Как можно применять Combine
Разберёмся, как с помощью Combine выполнить сетевой запрос и обработать данные:
- Создаётся Publisher, который оборачивает задачу получения данных для URL-запроса.
- Применяется оператор map. С помощью keyPath он извлекает свойство data и передаёт его в поток. Этот оператор похож на одноимённый оператор для массивов: позволяет преобразовывать входящие данные в другой тип.
- Происходит декодирование данных.
- Если на каком-то этапе произошла ошибка, она преобразуется в нужный тип с помощью mapError.
- Меняется очередь, на которой выполняются операции.
- Обрабатываются полученные данные.
- Cancelable экземпляр сохраняется в указанном сете. Если не сохранить, подписчик автоматически очистится после выполнения метода, и выполнение потока отменится. Чтобы этого не случилось, подписчик сохраняют в свойство класса.
Такой подход делает код более читаемым, управляемым и реактивным.
Полезные ссылки
Больше инсайтов мобильной разработки — в нашем Telegram-канале