Swift Combine - Часть 1 / 5 min

Это первая статья о работе со Swift Combine. Из неё вы узнаете о реактивном программировании и операциях преобразования, фильтрации и свертки сигналов.

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В этой статье вы прочитаете:

  • Что такое Combine и реактивное программирование

  • Операции: Mapping, Filtering, Reducing

Примеры кода из этой статьи доступны на GitHub

Что такое Combine и реактивное программирование

Swift Combine - это интегрированная в язык Swift реализация реактивного программирования.

Я не буду углубляться в теорию программирования, а опишу отличия с точки зрения отличий в коде для разработчика. Для примера возьмем строку поиска, в которой пользователь вводит данные и в блоке ниже будет выведен список подходящих объектов.

Пусть для простоты будут следующие операции:

  1. Получение ввода от пользователя, фильтрация 3х символов

  2. Формирование сетевого запроса для сервера и отправка запроса

  3. Расшифровка ответа

  4. Вывод результатов, сохраняя данные в переменную

В объектно-ориентированном императивном подходе на базе MVC у ViewController будет примерно так(Написал из головы):

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В случае с реактивным программированием, это будет выглядеть примерно так:

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Получается поток обработчиков, в которых нет "висячих" результатов работы. И прямой поток данных, и ветка ошибок должны иметь свой обработчик, иначе приложение не скомпилируется.

В целом плюсы реактивного подхода:

  1. Однозначность обработки положительных и отрицательных исходов

  2. Фильтрация событий по времени. В примере из всех вводов пользователя за 0.3 секунды будет взят последний

  3. Возможность собирать несколько потоков и разделять на несколько потоков. Например, можно отправить запрос к кэшу на диске и сети и взять тот, что вернется быстрее без ошибки

  4. Удобная связка со SwiftUI и Async благодаря тому, что Combine - часть языка, а не внешняя библиотека. Большая часть системных операций и UI также готова к работе с Combine

Источники данных и потребители

Эту тему я рассмотрю подробнее в будущей статье, но кратко опишу.

В Combine источники данных - это Publisher и дочерние типы. У основных системных вызовов, например у сетевых инструментов, SwiftUI и др. есть ответы и события в формате Publisher.

Результат работы можно назначить в свойство через метод assign или обработать как callback в методе sink. Они возвращают объект Cancellable, который надо сохрнить в переменной напрямую или через метод store, иначе память освободится и обработчик не будет работать.

Песочница для реактивных операций

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Для демонстрации работы операций и преобразований я сделал песочницу, в которой можно посмотреть, что подается на вход и что получается на выходе с помощью диаграмм. Про неё так же будет в будущих постах.

Песочница доступа в Github со всеми примерами, включая будущие статьи

Операции и преобразования

Данные в Combine можно преобразовывать, менять потоки, фильтровать. Все они логически разбиты на блоки. Ниже я опишу каждый блок, входящие в него методы, что они делают и приведу диаграммы, показывающие вход и выход.

У некоторых операций есть try... вариант, который позволяет вернуть ошибку, но я их дополнительно не описывал.

Mapping

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примеров

  • .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

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примеров

  • .filter - аналогично методу над массивом, возвращает только подходящие элементы. В примере не пропускает треугольники

  • .compactMap - аналгично map, но nil пропускается. В примере для сравнения map и compactMap на тех же данных

  • .removeDuplicates - удаляет одинаковые, идущие подряд элементы. Для этого они должны соответствовать протоколу Equitable. В примере синий и синяя звезда - разные.

  • .removeDuplicates(by: ) - удаляет одинаковые, идущие подряд элементы, определяя "одинаковость" в блоке. В примере одинаковость определяется оп цвету, В примере синий и синяя звезда - одно и то же.

  • .replaceEmpty - если поток закончился и ничего не вернул, то можно задать какое-то значение. В примере 1 возвращается оранжевый, а во втором были данные - возвращаются они

  • .replaceError - возвращает определенные данные, если была ошибка. В отличие от catch нет возможности обработать ошибку, она заменяется одним значением на входе

Reducing

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примеров

  • .collect - собирает объекты в массив. В примере три цветных объекта становится одним массивом

  • .collect(n) - собирает объекты в массив по несколько штук. В примере 6 объектов собираются в две кучки по три. В примере два в кучки 4 и 2

  • .collect(.byTime ) - собирает объекты по несколько штук, пришедших в указанный интервал

  • .collect(.byTimeOrCount ) - также собирает объекты по несколько штук за указанный интервал, но не больше указанного количества

  • .ignoreOutput - гинорирует то, что было в выдаче. Вернет только сигнал окончания потока или ошибку

  • .reduce - работет как scan, но на выходе будет только 1 объект, последний. В примере суммируются числа на объектах

Заключение

В этой статье кратко рассказано что такое Swift Combine и основные операции преобразования, фильтрации и свертки сигналов с визуализацией. Все визуализации получены в песочнице.

В будущих статьях я расскажу про операции для работы с несколькими потоками вместе, про входы/выходы и примеры использования.

Все работающие примеры preview есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.