Swift Combine - Часть 1 / 5 min
Это первая статья о работе со Swift Combine. Из неё вы узнаете о реактивном программировании и операциях преобразования, фильтрации и свертки сигналов.
В этой статье вы прочитаете:
Что такое Combine и реактивное программирование
Операции: Mapping, Filtering, Reducing
Примеры кода из этой статьи доступны на GitHub
Что такое Combine и реактивное программирование
Swift Combine - это интегрированная в язык Swift реализация реактивного программирования.
Я не буду углубляться в теорию программирования, а опишу отличия с точки зрения отличий в коде для разработчика. Для примера возьмем строку поиска, в которой пользователь вводит данные и в блоке ниже будет выведен список подходящих объектов.
Пусть для простоты будут следующие операции:
Получение ввода от пользователя, фильтрация 3х символов
Формирование сетевого запроса для сервера и отправка запроса
Расшифровка ответа
Вывод результатов, сохраняя данные в переменную
В объектно-ориентированном императивном подходе на базе MVC у ViewController будет примерно так(Написал из головы):
В случае с реактивным программированием, это будет выглядеть примерно так:
Получается поток обработчиков, в которых нет "висячих" результатов работы. И прямой поток данных, и ветка ошибок должны иметь свой обработчик, иначе приложение не скомпилируется.
В целом плюсы реактивного подхода:
Однозначность обработки положительных и отрицательных исходов
Фильтрация событий по времени. В примере из всех вводов пользователя за 0.3 секунды будет взят последний
Возможность собирать несколько потоков и разделять на несколько потоков. Например, можно отправить запрос к кэшу на диске и сети и взять тот, что вернется быстрее без ошибки
Удобная связка со SwiftUI и Async благодаря тому, что Combine - часть языка, а не внешняя библиотека. Большая часть системных операций и UI также готова к работе с Combine
Источники данных и потребители
Эту тему я рассмотрю подробнее в будущей статье, но кратко опишу.
В Combine источники данных - это Publisher и дочерние типы. У основных системных вызовов, например у сетевых инструментов, SwiftUI и др. есть ответы и события в формате Publisher.
Результат работы можно назначить в свойство через метод assign или обработать как callback в методе sink. Они возвращают объект Cancellable, который надо сохрнить в переменной напрямую или через метод store, иначе память освободится и обработчик не будет работать.
Песочница для реактивных операций
Для демонстрации работы операций и преобразований я сделал песочницу, в которой можно посмотреть, что подается на вход и что получается на выходе с помощью диаграмм. Про неё так же будет в будущих постах.
Песочница доступа в Github со всеми примерами, включая будущие статьи
Операции и преобразования
Данные в Combine можно преобразовывать, менять потоки, фильтровать. Все они логически разбиты на блоки. Ниже я опишу каждый блок, входящие в него методы, что они делают и приведу диаграммы, показывающие вход и выход.
У некоторых операций есть try... вариант, который позволяет вернуть ошибку, но я их дополнительно не описывал.
Mapping
.map - преобразование данных, количество на входе равно количеству на выходе. Можно вернуть объект того же типа или поменять тип. Аналогично методу map массива. В примере звезды заменяются треугольниками.
.mapError - преобразование ошибок, ошибка на входе - ошибка на выходе. Позволяет свести все ошибки к одному типу для обработки в конце. Обработка в блоке. В примере ошибка меняется на зеленую ошибку.
.replaceNil - замена nil на фиксированный объект. В примере nil заменяется оранжевой звездой
.scan - похоже на map, но результат передается как второй параметр в блок обработки. На первый шаг подается первый аргумент вызова scan. В примере подается объект-образец, в цвет которого будут покрашены все элементы
.setFailureType - позволяет поменять тип ошибки для синхронизации типов. Never поменять на свой тип. В примере в коде сначала ошибка приводится к Never с помощью Catch, а затем обратно к DemoError с помощью setFailureType.
.map Keypath - аналогично map, но можно передать 1 keypath, чтобы получить на выходе значение. Если передать 2 или 3 keypath, будет tuple. В примере 1 keypath .color используется для создания результатов, а в примере 2 .color и .icon используются для создания копий в результатах
Filtering
.filter - аналогично методу над массивом, возвращает только подходящие элементы. В примере не пропускает треугольники
.compactMap - аналгично map, но nil пропускается. В примере для сравнения map и compactMap на тех же данных
.removeDuplicates - удаляет одинаковые, идущие подряд элементы. Для этого они должны соответствовать протоколу Equitable. В примере синий и синяя звезда - разные.
.removeDuplicates(by: ) - удаляет одинаковые, идущие подряд элементы, определяя "одинаковость" в блоке. В примере одинаковость определяется оп цвету, В примере синий и синяя звезда - одно и то же.
.replaceEmpty - если поток закончился и ничего не вернул, то можно задать какое-то значение. В примере 1 возвращается оранжевый, а во втором были данные - возвращаются они
.replaceError - возвращает определенные данные, если была ошибка. В отличие от catch нет возможности обработать ошибку, она заменяется одним значением на входе
Reducing
.collect - собирает объекты в массив. В примере три цветных объекта становится одним массивом
.collect(n) - собирает объекты в массив по несколько штук. В примере 6 объектов собираются в две кучки по три. В примере два в кучки 4 и 2
.collect(.byTime ) - собирает объекты по несколько штук, пришедших в указанный интервал
.collect(.byTimeOrCount ) - также собирает объекты по несколько штук за указанный интервал, но не больше указанного количества
.ignoreOutput - гинорирует то, что было в выдаче. Вернет только сигнал окончания потока или ошибку
.reduce - работет как scan, но на выходе будет только 1 объект, последний. В примере суммируются числа на объектах
Заключение
В этой статье кратко рассказано что такое Swift Combine и основные операции преобразования, фильтрации и свертки сигналов с визуализацией. Все визуализации получены в песочнице.
В будущих статьях я расскажу про операции для работы с несколькими потоками вместе, про входы/выходы и примеры использования.
Все работающие примеры preview есть в проекте на Github, их можно скачать и запустить.
Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.