Сохранение бизнес-логики в Swift Combine. Часть 1
Перевод статьи подготовлен специально для студентов продвинутого курса «iOS Разработчик».
В предыдущей серии постов мы успешно построили платформу поверх SwiftUI, с помощью которой вы можете свободно наблюдать последовательности значений, проходящих через publisher Combine.
Однако проблема с этим решением — масштабируемость, когда нужно создать много паблишеров.
Мое решение этой проблемы заключается в том, что я должен как-то хранить эти паблишеры. Если я смогу как-то сериализовать их, я смогу их и сохранить. Если мне удастся сохранить их, я не только смогу модифицировать их без изменения кода, но также смогу поделиться ими с другими устройствами, поддерживающими Combine.
Хранение и передача операторов Combine
Очевидно, нам также необходимо иметь возможность преобразовывать сохраненные данные обратно в паблишер, но, кроме того, мы хотим иметь возможность обмениваться, передавать и распространять этих паблишеров с операторами из одного места в другое.
После того, как мы настроим такую структуру, как вы, возможно, уже догадались, в распределенной среде, централизованная служба может начать управлять вычислительной логикой для группы клиентов.
Структура Codable
Прежде чем двигаться дальше, чтобы понять, какие компоненты необходимы для структур, которые мы собираемся создать, давайте вспомним основной поток, который мы создали в предыдущей серии постов.
Поток чисел
Это самый простой поток; однако, если вы посмотрите глубже, вы заметите, что это не просто последовательность массивов. Каждый из круглых блоков имеет свой собственный оператор задержки ( delay ), который определяет фактическое время, когда он должен быть передан. Каждое значение в Combine выглядит так:
И в целом все это выглядит так:
Следовательно мы узнаем две вещи из наших наблюдений.
Создаем свой StreamItem
Значение потока должно быть универсальным, чтобы вмещать значения любого типа.
Создаем свою StreamModel
Создаем структуру оператора
Мы рассматриваем оператор delay как перечисление ( enum ) с одним связанным значением, чтобы хранить время задержки.
Теперь у нас есть хорошая структура для представления этого последовательного потока, который генерирует значения от 1 до 4 с секундным интервалом задержки.
Конвертируем StreamModel в Publisher
Теперь мы создали экземпляр потока; однако, если мы не преобразуем его в паблишер, все окажется бессмысленным. Давайте попробуем.
Прежде всего, каждая модель оператора ссылается на фактический оператор Combine, который должен добавить к данному паблишеру и вернуть эксплуатируемый паблишер.
На уровне StreamModel мы получаем паблишера всего потока.
Вы правильно догадались: мы используем метод append для объединения паблишеров.
Визуализация, редактирование и снова визуализация потока
Смотрите демо ниже. Теперь мы можем вносить изменения в поток без изменения кода.
Следующая глава: Сериализация/десериализация фильтров и операторов карт
В следующей части мы собираемся добавить больше операторов в перечисление operator и начать применять их на уровне потока.
До следующего раза, вы можете найти исходный код здесь в этом репозитории combine-magic-swifui в папке combine-playground.
Ждем ваши комментарии и приглашаем на открытый вебинар по теме «iOS-приложение на SwiftUI с использованием Kotlin Mobile Multiplatform».
Начинаем работу с Combine
Combine, анонсированный на WWDC 2019, представляет собой новую «реактивную» платформу Apple для обработки событий в течение времени. Вы можете использовать Combine для унификации и упрощения вашего кода для работы с такими вещами, как делегаты, уведомления, таймеры, блоки завершения и обратные вызовы.
Combine, анонсированный на WWDC 2019, представляет собой новую «реактивную» платформу Apple для обработки событий в течение времени. Вы можете использовать Combine для унификации и упрощения вашего кода для работы с такими вещами, как делегаты, уведомления, таймеры, блоки завершения и обратные вызовы. Некоторое время на iOS работали сторонние реактивные фреймворки, но теперь Apple создала свой собственный.
В этом руководстве вы узнаете, как:
Вы увидите эти ключевые концепции в действии, улучшив FindOrLose — игру, в которой вам предстоит быстро определить одно изображение, которое отличается от трех других.
Готовы исследовать волшебный мир Combine в iOS? Поехали!
Начинаем работу с Combine
Загрузите материалы проекта, нажав кнопку «Загрузить материалы» на этой странице.
Откройте начальный проект и проверьте файлы проекта.
Прежде чем вы сможете играть в игру, вы должны зарегистрироваться на портале разработчиков Unsplash, чтобы получить ключ API. После регистрации вам в нем нужно будет создать приложение. По завершении вы увидите такой экран:
Примечание. API-интерфейсы Unsplash имеют ограничение на скорость 50 вызовов в час. Наша игра веселая, но, пожалуйста, не играйте в нее слишком много :]
Откройте UnsplashAPI.swift и добавьте свой ключ API Unsplash в UnsplashAPI.accessToken следующим образом:
Соберите и запустите проект. На главном экране отображаются четыре серых квадрата. Вы также увидите кнопку для запуска или остановки игры:
Нажмите Play, чтобы начать игру:
Сейчас это полностью рабочая игра, но взгляните на playGame() в GameViewController.swift. Метод заканчивается так:
Слишком много вложенных структур. Можете ли вы понять, что происходит и в каком порядке? Что, если вы хотите изменить порядок выполнения, выйти из потока или добавить новые функции? Тут и придет на помощь Combine!
Введение в Combine
Платформа Combine предоставляет декларативный API для обработки данных в течение времени. Есть три основных компонента:
Разберемся с каждым компонентом по очереди.
Издатели
Объекты Publisher последовательно доставляют значения во времени. Протокол имеет два связанных типа: Output (выход), тип создаваемого значения, и Failure (отказ), тип ошибки, с которой он может столкнуться.
Каждый Издатель может создавать несколько событий:
Операторы
Операторы — это специальные методы, которые вызываются Издателями и возвращают того же или другого издателя. Оператор описывает поведение при изменении значений, добавлении значений, удалении значений или многих других операций. Вы можете связать несколько операторов вместе для выполнения сложной обработки.
Подумайте о значениях, исходящих от первоначального издателя через серию операторов. Как река, значения исходят от вышестоящего издателя и текут к нижележащему издателю.
Подписчики
Издатели и Операторы бессмысленны, если кто-то не слушает публикуемые события. Это что-то и есть Подписчик.
Подписчик получает от издателя поток значений, завершение или отказ.
Собираем все вместе
После этого Издатель может отправлять значения Подписчику. Он может отправить полное количество запрошенных значений, но может также отправить меньше. Если Издатель конечный, он в конечном итоге вернет событие завершения или, возможно, ошибку. Эта диаграмма обобщает процесс:
Работа в сети с помощью Combine
Это был быстрый обзор Combine. Пора использовать это в собственном проекте!
Во-первых, вам нужно создать перечисление GameError для обработки всех ошибок Издателя. В главном меню Xcode выберите Файл ▸ Новый ▸ Файл… и выберите шаблон iOS ▸ Источник ▸ Файл Swift.
Назовите новый файл GameError.swift и добавьте его в папку Game.
Теперь добавьте перечисление GameError :
Теперь вы готовы обрабатывать коды состояния HTTP и ошибки декодирования.
Импортируйте Combine. Откройте UnsplashAPI.swift и добавьте в начало файла следующее:
Затем измените randomImage(completion:) на следующее:
AnyPublisher — это системный тип, который можно использовать для обертывания “any” издателя, что избавляет вас от необходимости обновлять сигнатуры методов, если вы используете операторы или если вы хотите скрыть детали реализации от вызывающих.
Это похоже на большой объем кода, но он использует множество функций Combine. Вот пошаговая инструкция:
Вы могли бы написать почти все это с помощью одного оператора, но это не совсем в духе Combine. Думайте об этом как об инструментах UNIX, каждый шаг выполняет одно действие и передает результаты.
Загрузка изображения с помощью Combine
Теперь, когда у вас есть сетевая логика, пришло время загрузить несколько изображений.
Откройте файл ImageDownloader.swift и импортируйте Combine в начале файла со следующим кодом:
Во многом этот код похож на предыдущий пример. Вот пошаговая инструкция:
Использование Zip
На этом этапе вы изменили все методы работы в сети, чтобы использовать Издателей вместо блоков завершения. Теперь вы готовы их использовать.
Откройте GameViewController.swift. Импортируйте Combine в начало файла:
Добавьте следующее свойство в начало класса GameViewController :
Вы будете использовать это свойство для хранения всех ваших Подписок. Пока вы имели дело с Издателями и Операторами, но еще ничего не подписалось.
В приведенном выше коде вы:
Затем вы воспользуетесь той же логикой, чтобы получить второе изображение. Добавьте это сразу после firstImage :
На этом этапе вы загрузили два случайных изображения. Теперь пришло время, простите за каламбур, объединить их. Для этого нужно использовать zip. Добавьте следующий код сразу после secondImage:
Наконец, собираем и запускаем!
Поздравляем, теперь ваше приложение успешно использует Combine для обработки потоков событий!
Современный код для выполнения HTTP запросов в Swift 5 с помощью Combine и применение их в SwiftUI. Часть 1
Появление в Swift 5 нового фреймворка функционального реактивного программирования Combine в сочетании с уже существующими URLSession и Codable предоставляет вам все необходимые инструменты для самостоятельного написания очень компактного кода для выборки данных из интернета.
При обращении к такого рода сервисам могут возникать ошибки, например, связанные с тем, что вы задали неправильный ключ API-key или превысили допустимое количество запросов или еще что-то. Необходимо обрабатывать такого рода ошибки, иначе вы рискуете оставить пользователя в полном недоумении с пустым экраном. Поэтому надо уметь не только выбирать с помощью Combine данные из интернета, но и сообщать об ошибках, которые могут возникнуть при выборке, и управлять их появлением на экране.
Код приложения для данной статьи находится на Github.
Модель данных и API сервиса NewsAPI.org
То, какие статьи или источники информации мы хотим выбирать с сервера NewsAPI.org, будем указывать с помощью перечисления enum Endpoint:
Он возвращает нам «издателя» AnyPublisher со значением в виде массива источников информации [Source] и отсутствием ошибки Never (в случае ошибок возвращается пустой массив источников [ ] ).
Это позволит упростить предыдущие два метода:
В этой статье мы сосредоточимся на том, как сделать нашу Модель для SwiftUI внешним «источником истины» (source of truth).
Давайте сначала рассмотрим, как в SwiftUI должны функционировать полученные «издатели» на конкретном примере отображения различного рода статей:
Как мы будем это делать?
Для создания нового «издателя» на основе данных, полученных от предыдущего «издателя» в Combine используется оператор flatMap :
Далее мы «подписываемся» на этого вновь полученного «издателя» с помощью очень простого «подписчика» assign (to: \.articles, on: self) и присваиваем полученное от «издателя» значение @Published массиву articles :
Мы только что создали в init( ) АСИНХРОННОГО «издателя» и «подписались» на него, в результате получив AnyCancellable «подписку» и это легко проверить, если мы сохраним нашу «подписку» в константе let subscription :
Таким образом мы можем получать массивы статей для всех трех опций — «topHeadLines», «search» и «from category»:
. но для фиксированной и заданной по умолчанию поисковой строки searchString = «sports» (там, где она требуется):
Однако для опции «search» необходимо предоставить пользователю текстовое поле SearchView для ввода поисковой строки:
В результате пользователь сможем искать любые новости по набранной поисковой строке:
Для опции «from category» необходимо предоставить пользователю возможность выбрать категорию и начинаем мы с категории science :
Мы видим, как очень простая ObservableObject Модель, имеющая два управляемых пользователем @Published свойства — indexEndpoint и searchString — позволяет выбрать широкий спектр информации с сайта NewsAPI.org.
Список источников информации
Мы получим список источников информации для различных стран:
… и возможность поиска их по названию:
… а также детальную информацию о выбранном источнике: его имя, категорию, страну, краткое описание и ссылку на сайт:
Если кликнуть на ссылке, то попадем на сайт этого источника информации.
Для того, чтобы все это работало нужна предельно простая ObservableObject Модель, имеющая всего два управляемых пользователем @Published свойств — searchString и country :
В результате получим нужное нам View :
Вообще, если вы посмотрите на всё приложение NewsApp, то нигде не увидите, чтобы мы явно запрашивали выборку статей или источников информации с сайта NewsAPI.org. Мы управляем только @Published данными, а View Model делает свою работу: выбирает нужные нам статьи и источники информации.
Модель статьи Article содержит URL сопровождающего её изображения urlToImage :
На основании этого URL мы в дальнейшем должны получить само изображения UIImage с сайта NewsAPI.org.
Используем оператор flatMap и очень простого «подписчика» assign (to: \image, on: self) с целью присваивания полученного от «издателя» значения свойству @Published image :
«Издателя» AnyPublisher на основе url создаем в функции fetchImageErr (for url: URL?) :
Далее мы выполним следующие шаги, учитывая при этом все возможные ошибки (мы не будем идентифицировать ошибки, нам просто важно знать, что ошибка есть):
Напоследок мы упакуем все наши возможности показа данных с агрегатора новостей NewsAPI.org в TabView :
Отображение ошибок при выборке и декодировании JSON данных с сервера NewsAPI.org.
При обращении к серверу NewsAPI.org могут возникать ошибки, например, связанные с тем, что вы задали неправильный ключ API-key или, имея тариф разработчика, который ничего не стоит, превысили допустимое количество запросов или еще что-то. При этом сервер NewsAPI.org снабжает вас HTTP кодом и соответствующим сообщением:
Необходимо обрабатывать такого рода ошибки сервера. Иначе пользователь вашего приложения попадёт в ситуацию, когда вдруг ни с того, ни с сего, сервер NewsAPI.org перестанет обрабатывать какие-либо запросы, оставляя пользователя в полном недоумении с пустым экраном.
Затем мы должны установить для полученного «издателя» ТИП ошибки равный требуемому NewsError :
И ещё добавляем показ экстренного сообщения Alert для случая появления ошибки.
Например, если неверный API ключ:
… то мы получим такое сообщение:
Если лимит запросов исчерпан, то мы получим такое сообщение:
Как мы знаем, этот код очень легко использовать для получения конкретного «издателя», если исходными данными для url является Endpoint для агрегатора новостей NewsAPI.org или страна country источника информации, а на выходе требуются различные Модели — например, список статей или источников информации:
Заключение.
Если нужно учитывать ошибки, то код для Generic «издателя» немного усложнится, но все равно это будет очень простой код без каких-либо callbacks:
Полученные всевозможные «издатели» можно очень просто «заставить работать» в ObservableObject классах, которые с помощью своих @Published свойств управляют вашим UI, спроектированным с помощью SwiftUI. Эти классы обычно играют роль View Model, так как в них есть так называемые «входные» @Published свойства, соответствующие активным UI элементам (текстовым полям TextField, Stepper, Picker, переключатели Toggle и т.д.) и «выходные» @Published свойства, состоящие в основном из пассивных UI элементов (текстов Text, изображений Image, геометрических фигур Circle(), Rectangle() и т.д.
Этой идеей пронизано всё приложение для работы с агрегатором новостей NewsAPI.org, представленное в этой статье. Она оказалась достаточно универсальной и использовалась при разработке приложения для базы данных фильмов TMDb и агрегатора новостей Hacker News, о которых будет рассказано в следующих статьях.
Код приложения для данной статьи находится на Github.
1. Хочу обратить ваше внимание на то, что если вы пользуетесь симулятором для приложения, представленного в этой статье, то знай, что NavigationLink на симуляторе работает с ошибкой. Вы можете воспользоваться NavigationLink на симуляторе только 1 раз. Т.е. вы использовали ссылку, вернулись назад, кликаете на ту же ссылку — и ничего не происходит. До тех пор пока вы не воспользуетесь другой ссылкой, первая не заработает, зато вторая станет недоступной. Но такое наблюдается только на симуляторе, на реальном устройстве все работает нормально.
2. Некоторые источники информации все еще используют http вместо https для «картинок» своих статей. Если вы определённо хотите увидеть эти «картинки», но не можете контролировать источник их появления, то вам придётся настроить систему безопасности ATS ( App Transport Security) на получение этих http «картинок», но это, конечно, не самая хорошая идея. Можно воспользоваться более безопасными вариантами.
Готовимся к Combine
Полтора года назад я пел дифирамбы RxSwift. У меня ушло какое-то время, чтобы разобраться в нем, но когда это случилось, пути назад больше не было. Теперь у меня был самый лучший молоток в мире, и будь я проклят, если всё вокруг не казалось мне гвоздём.
На летней конференции WWDC Apple представила фреймворк Combine. На первый взгляд, он выглядит как немного более лучшая версия RxSwift. Прежде чем я смогу объяснить, что мне в нём нравится, а что нет, нам нужно понять, какую проблему призван решить Combine.
Реактивное программирование? И что?
Сообщество ReactiveX — частью которого является сообщество RxSwift — объясняет его суть так:
API для асинхронного программирования с наблюдаемыми потоками.
ReactiveX — это комбинация лучших идей из шаблонов проектирования «наблюдатель» (Observer) и «итератор» (Iterator), а также функционального программирования.
И что все же это на самом деле означает?
Основы
Чтобы действительно понять суть реактивного программирования, я считаю полезным разобраться, как мы к нему пришли. В этой статье я опишу, как можно взглянуть на существующие типы в любом современном ООП-языке, покрутить их и затем прийти к реактивному программированию.
В этом материале мы быстро углубимся в дебри, что не является абсолютно необходимым для понимания реактивного программирования.
Однако я считаю это любопытным академическим упражнением, особенно с точки зрения того, как сильно типизированные языки могут вести нас к новым открытиям.
Так что ждите моих следующих записей, если вам будут интересны новые подробности.
Enumerable
Известное мне «реактивное программирование» зародилось в языке, на котором я когда-то писал — C#. Предпосылка сама по себе довольно проста:
Что если вместо того, чтобы извлекать значения из enumerable, те сами будут отправлять вам значения?
Эту идею, «push вместо pull», лучше всего описали Брайан Бекман и Эрик Мейер. Первые 36 минут… я ничего не понял, но начиная примерно с 36-й минуты становится действительно интересно.
Короче, давайте переформулируем идею линейной группы объектов в Swift, а также объекта, который может итерировать по этой линейной группе. Сделать это можно с помощью определения этих фейковых Swift-протоколов:
Двойники
Давайте теперь всё это перевернём и сделаем двойников. Будем отправлять данные туда, откуда они приходили. И получать данные оттуда, откуда они уходили. Звучит абсурдно, но потерпите немного.
Двойник Enumerable
Начнём с Enumerable:
Знаю, это странно. Не уходите.
Двойник Enumerator
Здесь есть несколько проблем:
Мы можем сделать ещё кое-что: взгляните на сигнатуру завершения перебора:
Вероятно, со временем будет проиcходить что-то подобное:
А теперь давайте всё упростим и будем вызывать enumeratorIsDone лишь тогда, когда… всё будет действительно готово. Руководствуясь этой идеей, упростим код:
Приберёмся за собой
Поместим DualOfEnumerator сюда:
Вот какой двойник получится в итоге:
Хоть розой назови, хоть нет
Итак, еще один раз, вот что у нас получилось:
Давайте теперь поиграемся немного с именами.
Так гораздо лучше и понятнее.
А что насчёт имён типов? Они просто ужасны. Давайте поменяем их немного.
Ух ты
Эти два типа лежат в основе RxSwift и реактивного программирования.
Насчёт «фейковых» протоколов
Упомянутые мной выше два «фейковых» протокола на самом деле вовсе не фейковые. Это аналоги существующих типов в Swift:
Так о чём волноваться?
Так много в современной разработке — особенно разработке приложений — связано с асинхронностью. Пользователь внезапно нажал на кнопку. Пользователь внезапно выбрал вкладку в UISegmentControl. Пользователь внезапно выбрал вкладку в UITabBar. Веб-сокет внезапно дал нам новую информацию. Это скачивание внезапно — и наконец-то — завершилось. Эта фоновая задача внезапно завершилась. Этот список можно продолжать до бесконечности.
В современном мире CocoaTouch есть множество способов обработки подобных событий:
А теперь представьте, если был бы целый набор функций, позволяющий модифицировать эти потоки, преобразовывать их из одного типа в другой, извлекать информацию из Element’ов, или даже комбинировать их с другими потоками.
Внезапно в наших руках оказывается новый универсальный набор инструментов.
И вот, мы вернулись к началу:
API для асинхронного программирования с наблюдаемыми потоками.
Именно это делает RxSwift таким мощным средством. Как и Combine.
Что дальше?
Если вы хотите побольше прочитать об RxSwift на практике, то рекомендую мои пять статей, написанные в 2016-м. В них описывается создание простейшего CocoaTouch-приложения, с последующим поэтапным преобразованием в RxSwift.
В одной из следующих статей я расскажу, почему многие из методик, описанных в моём цикле статей для начинающих, не применимы в Combine, а также сравню Combine с RxSwift.
Combine: в чём суть?
Обсуждение Combine подразумевает и обсуждения основных различий между ним и RxSwift. Для меня их три:
Возможности RxCocoa
В одном из предыдущих постов я говорил, что RxSwift нечто большее, чем… RxSwift. Он предоставляет многочисленные возможности по использованию контролов из UIKit в типа-но-не-совсем подпроекте RxCocoa. Кроме того, RxSwiftCommunity пошли дальше и реализовали много привязок для ещё более укромных закоулков UIKit, а также некоторые другие классы CocoaTouch, которые пока не покрывает RxSwift и RxCocoa.
Поэтому очень легко можно получить Observable поток из, скажем, нажатия на UIButton. Еще раз приведу этот пример:
Давайте (наконец-то) все же поговорим о Combine
Combine очень похож на RxSwift. Как сказано в документации:
Фреймворк Combine предоставляет декларативный Swift API для обработки значений с течением времени.
Звучит знакомо: вспомним описание ReactiveX (родительского проекта для RxSwift):
API для асинхронного программирования с наблюдаемыми потоками.
В обоих случаях говорится об одном и том же. Просто в описании ReactiveX используются специфические термины. Его можно переформулировать так:
API для асинхронного программирования со значениями в течение времени.
Практически то же самое, как по мне.
То же самое, что и раньше
Когда я начал анализировать API, то стало сразу очевидно, что большинство известных мне типов из RxSwift имеют похожие варианты в Combine:
«Перерыв на какавушку»
Всё меняется, как только начинаешь углубляться в RxCocoa. Вспомните вышеприведённый пример, в котором мы хотели получить поток Observable, который представляет нажатия на UIButton? Вот он:
Чтобы сделать то же самое в Combine, требуется… гораздо больше работы.
Combine не предоставляет никаких возможностей по привязке к UIKit-объектам.
Это… просто нереальный облом.
Вот обычный способ получения UIControl.Event из UIControl с помощью Combine:
Тут… намного больше работы. Хотя бы вызов выглядит похоже:
Для сравнения, RxCocoa предоставляет приятное, вкусное какао в виде привязок к UIKit-объектам:
Для сравнения, мой ControlPublisher появился только… сейчас. Только из-за количества клиентов (ноль) и времени использования в реальном мире (практически ноль по сравнению с RxCocoa) мой код можно считать бесконечно опаснее.
Помощь сообщества?
Честно говоря, сообществу ничто не мешает создать свой open source «CombineCocoa», который бы заполнил пробел RxCocoa так же, как это сделало RxSwiftCommunity.
Тем не менее, я считаю это огромным минусом Combine. Я не хочу переписывать весь RxCocoa, только чтобы получить привязки к UIKit-объектам.
Если я решу сделать ставку на SwiftUI, то, полагаю, это избавит от проблемы отсутствия привязок. Даже моё маленькое приложение содержит кучу UI-кода. Выкинуть всё это только для того, чтобы запрыгнуть на поезд Combine, будет как минимум глупо, а то и опасно.
К слову, в статье из документации Receiving and Handling Events with Combine кратко описывается, как получать и обрабатывать события в Combine. Введение хорошее, в нем показывается, как извлекать значение из текстового поля и сохранять его в кастомном объекте модели. Документация также демонстрирует использование операторов для выполнения некоторых более продвинутых модификаций рассматриваемого потока.
Пример
Перейдём в конец документации, где приведён пример кода:
С этим у меня… куча проблем.
Уведомляю вас, что мне это не нравится
Больше всего вопросов у меня вызывают первые две строки:
NotificationCenter — это что-то вроде шины приложения (или даже системной шины), в которой много кто может забросить данные, или поймать кусочки пролетающей мимо информации. Это решение из категории всё-и-для-всех, как и было задумано создателями. И действительно есть много ситуаций, когда вам может быть необходимо узнать, скажем, что клавиатура была только что показана или скрыта. NotificationCenter — отличный способ распространения этого сообщения по всей системе.
Но для меня NotificationCenter — это код с душком. Бывают случаи (вроде получения уведомления про клавиатуру), когда NotificationCenter в самом деле является лучшим возможным решением проблемы. Но слишком часто для меня NotificationCenter — самое удобное решение. Действительно очень удобно закинуть что-то в NotificationCenter и забрать это что-то в другом месте приложения.
Кроме того, NotificationCenter «строко»-типизирован, то есть можно легко допустить ошибку, какое уведомление пытаешься опубликовать или слушать. Swift делает всё возможное, чтобы улучшить ситуацию, но под капотом до сих пор кроется все тот же NSString.
По поводу KVO
На платформе Apple уже давно есть популярный способ получения уведомлений об изменениях в разных частях кода: key-value observation (KVO). Apple описывает его так:
Это механизм, позволяющий объектам получать уведомления об изменениях в заданных свойствах других объектов.
Благодаря твиту Gui Rambo я заметил, что Apple добавила в Combine привязки к KVO. Это могло означать, что я смогу избавиться от многочисленных огорчений по поводу отсутствия в Combine аналога RxCocoa. Если я смогу использовать KVO, это, вероятно, устранит потребность в «CombineCocoa», если можно так выразиться.
Попробовал сообразить пример использования KVO для получения значения из UITextField и вывода его в консоль:
Выглядит неплохо, идем дальше?
Не так быстро, друзья.
UIKit, по большому счёту, не совместим с KVO.
А без поддержки KVO моя идея не сработает. Мои проверки это подтвердили: код ничего не выводит в консоль, когда я ввожу текст в поле.
Итак, мои надежды на избавление от потребности в UIKit-привязках были прекрасны, но недолги.
Очистка
Другая проблема Combine заключается в том, что пока что совершенно неясно, где и как нужно освобождать ресурсы в Cancellable объектах. Кажется, что мы должны хранить их в переменных экземпляра. Но не припоминаю, чтобы в официальной документации что-то говорилось об очистке.
В RxSwift есть ужасно-названный-но-невероятно-удобный DisposeBag. И не менее легко и создать CancelBag в Combine, но не совсем уверен в том, что в данном случае это самое лучшее решение.
В следующей статье мы поговорим об обработке ошибок в RxSwift и Combine, о достоинствах и недостатках обоих подходов.










