RxSwift в примерах. Часть I — основы

Reactive Programming logo
{lang: 'ru'}

RxSwift является хайповой темой уже давно. Я бы даже сказал, что прошедший 2016 год был годом Rx: многие мои знакомые и коллеги так или иначе начали с ним работать или хотя бы попробовали. Чтобы самому лучше разобраться в теме и закрепить уже имеющиеся знания, я решил перевести цикл статей, где Лукаш Мроз (Łukasz Mróz) рассказывает в примерах о том, как использовать RxSwift.

Swift – язык, который приятен, что бы вы с его помощью ни делали. Он хорошо объединяет аспекты других языков, что делает Swift действительно гибким и относительно легки для понимания новичками. Поэтому его используют не только с Объектно-Ориентированным Программированием (ООП), но и с другими парадигмами, вроде новейшего Протокол-Ориентированного Программирования, представленного на WWDC 2015. Вам не нужно много искать, чтобы обнаружить, что в Swift вы можете также использовать Функциональное Программирование и Реактивное Программирование. Сегодня мы поговорим о комбинации двух последних: Функциональном Реактивном Программировании.

Итак, что такое Функциональное Реактивное Программирование? Если коротко, то это – использование Реактивного Программирование с частями Функционального Программирования (filter, map, redice и т.д.). Причём, они уже встроены в Swift! А реактивная часть обеспечивается RxSwift.

RxSwift – это версия Swift с реактивными расширениями, написанными на нём самом.

ReactiveX – это комбинация лучших идей паттерна “Наблюдатель”, “Итератор” и функционального программирования.

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

Вы можете спросить, “Почему мне бы вдруг захотелось это использовать?”. Ответ очень прост. Это делает вашу работу проще. Вместо сообщений, который сложно тестировать, мы можем использовать сигналы. Вместо делегатов, который занимают много места в коде, мы можем писать блоки и удалить многочисленные switch и if/else. У нас также есть KVO, IBActions, фильтры ввода, MVVM и много-много других вещей, которые отлично управляются RxSwift. Но помните, что это не всегда лучший способ решения проблем, но вы всё равно должны знать, когда лучше его использовать, чтобы полностью раскрыть потенциал RxSwift. Я попробую показать вам некоторые примеры, которые вы можете использовать в своём приложении.

Определения

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

Ваш смартфон – наблюдаемый (observable). Он подаёт сигналы (signals) вроде оповещений фейсбука, смс и так далее. Вы естественным образом подписаны (subscribe) на них, поэтому вы видите каждое оповещение на вашем домашнем экране. Теперь вы можете решать, что делать с этим сигналом (signal). Вы – наблюдатель (observer).

Теперь вы полностью подготовлены к примеру ниже.


Пример

Мы напишем Поисковик Городов – при печати названия города в поисковой строке он будет динамически показывать нам список. В этот момент он будет пытаться найти те города, которые начинаются с данных букв и отображать их в таблице (TableView). Довольно просто, не правда ли? Когда вы пытаетесь сделать динамический поиск в вашем приложении, всегда нужно думать о том, что может пойти не так. Например, что если я буду писать очень быстро или буду часто менять своё желание? В этом случае было бы слишком много запросов к API, которые нужно было бы фильтровать. В реальном приложении вам нужно было бы отменить предыдущий запрос, подождать какое-то время перед отправкой другого, проверить фразу на случай, если она такая же, как до этого, и так далее. Часто это приводит к огромной логике, которая выглядит довольно просто на первый взгляд. “Это всего лишь динамический поиск, что может пойти не так?”. Конечно, его можно реализовать без использования Rx, но давайте посмотрим, как мы может написать эту логику, используя совсем немного кода.

Сначала нам нужно создать новый проект. Затем установить CocoaPods и RxSwift + RxCocoa. Пример Podfile для этого представлен ниже:

Если вы подготовили все инструменты, можно начинать писать код!

Мы создадим нам просто интерфейс с UISearchBar + UITableView.

UISearchBar UITableView-390x404

Затем нам понадобится массив для хранения наших городов. Чтобы уменьшить количество логики в коде, мы не будем использовать API, которое будет показано в следующих постах. Вместо этого, мы будем использовать два массива, один из которых будет хранить список всех городов, а другой – только показываемые города.

Потом найстроим UITableViewDataSource и соединим его с нашим массивом shownCities:

Теперь всё должно начать работать как обычный UITableView. Так что, если мы изменим имена городов в shownCities, мы должны заметить это на экране.

Теперь более интересное. Сейчас мы будем наблюдать (observe) текст в поисковой строке UISearchBar. Это легко, потому что  эта возможность встроена RxCocoa (являющуюся расширением RxSwift)! UISearchBar и много других компонентов Cocoa фреймворка имеют поддержку от команды Rx. В нашем случае, с использованием UISearchBar, мы можем использовать его свойство rx_text, которое посылает сигналы, как только текст в поисковой строке изменится. Но как наблюдать (observe) за этим? Очень просто! Сначала нужно импортировать RxCocoa и RxSwift.

Затем, в методе viewDidLoad() мы добавим наблюдение к свойству rx_text у UISearhBar:

Отлично! Теперь наш динамический поиск работает! В subscribeNext мы подписываемся на наблюдаемое свойство, которое производит сигналы. В данном случае, нам нужно только новые значения, но subscribe имеет несколько обёрток, включая события вроде onError, onCompleted и т.д.

Наиболее интересная вещь происходит в последней строке. Когда вы подписываетесь на наблюдаемых, иногда бывает нужно отписаться от них, чтобы избежать цикла владения (retain cycles). В Rx у нас есть нечто, называемое DisposeBag, которое обычно используется для хранения всех сущностей, от которых нужно отписаться в процессе деинициализации deinit(). В следующий раз я покажу вам как вы можете использовать небольшую библиотеку, чтобы помочь себе с этим, но сейчас нам нужно создать корзину, чтобы нормально скомпилировать проект:

Теперь, после компиляции, приложение должно работать! После набора “O” мы должны получить запись с Oslo  в таблице. Отлично! Но… Как на счёт того, чего мы боялись? Спама API? Пустых фраз? Задержки? Правильно, мы должны защитить себя. Давайте начнём с защиты нашего API бэкенда. Нам нужно добавить задержку, которая будет запускать запрос через X секунд после начала печати, но только если фраза не изменилась. Обычно, мы бы использовали NSTimer, который бы инвалидировался, если была набрана новая фраза. Не так сложно, но всё ещё здесь можно ошибиться. А что если мы напечатаем “O”, появятся результаты, потом мы передумаем и напишем “Oc”, но опять вернёмся к “O”, ровно перед задержкой и API всё равно будет вызвано?! В этом случае у нас будет ровно два абсолютно одинаковых запроса к API. В некоторых случаях нам нужно такое поведение, потому что, возможно, база данных обновляется очень быстро. Но как правило, нет необходимости вызывать два одинаковых запроса ранее, чем через, допустим, 0.5 секунд. Чтобы сделать это без Rx, на потребовалось бы добавить флаги, последние запросы и сравнивать из с новыми. Опять же, не так много кода, но логика бы росла и росла. В RxSwift мы можем сделать это в две строчки кода. throttle() создаёт эффект задержки для данного диспетчера, а distinctUntilChanged() защищает нас от тех же самых значений. Если мы соединим это с предыдущий версией кода, всё вместе должно выглядеть так:

Однако, мы забыли ещё кое-что. Что если пользователь напечатал что-то, обновил таблицу и удалил свою фразу, сделав поле пустым? Да, мы пошлём запрос с пустым параметром… Мы этого не хотим, поэтому нужно как-то защититься от этого. Как? Используя filter(). Вы, возможно, знакомы с ним, поскольку он встроен в Swift. Но возникает вопрос: “Почему я должен использовать фильтр на значении? Ведь filter() работает на коллекциях!!!” И это очень хороший вопрос! Но не думайте о Наблюдаемом (Observable) как о значении. Это поток значений, который будет в конечном итоге продолжаться. И поэтому вам будет легче понять использование функциональных блоков. Фильтруя наши значения, мы будем делать это как если бы это был массив строк:

Вот и всё! Полный код, покрывающий на самом деле не самую простую логику, состоит всего из девяти строк. Магия!

На сегодня всё. Полный проект доступен на github


Оригинал: RxSwift by Examples #1 – The basics.


Полезная статья? Их будет больше, если вы поддержите меня!