Как стать автором
Обновить

Переходим В OFFLINE FIRST с использованием Core Data и Managed Document(s)

Блог компании МегаФон Блог компании Конференции Олега Бунина (Онтико) Разработка мобильных приложений *Разработка под Android *Разработка под MacOS *
Придя в компанию МегаФон как iOS-разработчик, Валентин Чернов попал в основной сегодняшний тренд — переход в офлайн: Валентин занимается разработкой мобильного личного кабинета — главного приложения МегаФона. Оно позволяет видеть баланс, менять тариф, подключать и отключать услуги и сервисы, участвовать в конкурсах и пользоваться персональными предложениями партнеров МегаФона.

МегаФон выбрал возможность работать при нестабильной связи как одну из важных точек роста. В России есть места, где связь временно отключается или надолго пропадает. И нужно, чтобы даже в этом случае приложение работало без сбоев.

О том, как эта задача выполнялась в течение последних пяти месяцев, как выбирали и воплощали архитектуру проекта, какие технологии использовались, а также чего достигли и что было запланировано на будущее, Валентин рассказал в докладе на Конференции разработчиков мобильных приложений Apps Live 2020.



Задача


Бизнес сказал — идём в оффлайн, чтобы пользователь мог успешно взаимодействовать с приложением в условиях нестабильного сетевого подключения. Мы, как команда разработки, должны были гарантировать Offline first — работу приложения даже при нестабильном или совсем отсутствующем интернете. Сегодня расскажу о том, с чего мы начали и какие первые шаги в этом направлении сделали.

Стек технологий


Помимо стандартной архитектуры MVC мы используем:

Swift + Objective-C


Большая часть кода (80% нашего проекта) написан на Objective-C. А уже новый код мы пишем на Swift.

Модульная архитектура


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

Submodules (библиотеки)


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

Core Data для локального хранения информации


При выборе главным критерием для нас были нативность и интеграция с iOS фреймворками. А эти преимущества Core Data стали решающими:

  • Автосохранение стека и данных, которые получаем;
  • Удобная работа с моделью данных, достаточно удобная работа с графическим редактором для составления сущностей (и возможности их править, передавать новым разработчикам и т.д.)
  • Поддержка миграции и версионирования;
  • Ленивая загрузка объектов;
  • Работа в многопоточном режиме;
  • Отслеживание изменений;
  • Интеграция с UI (FRC);
  • Работа с запросами в БД на более высоком уровне (NSPredicates).

UIManaged document


У UI kit есть встроенный класс, называемый UIManagedDocument, и который является подклассом UIDocument. Его основное отличие — при инициализации управляемого документа указывается URL-адрес для расположения документа в локальном или удаленном хранилище. Затем объект документа полностью создает стек Core Data прямо из коробки, который используется для доступа к постоянному хранилищу документа с использованием объектной модели (.xcdatamodeld) из основного пакета приложения. Это удобно и имеет смысл, даже несмотря на то, что мы живем уже в 21 веке:

  • UIDocument автосохраняет текущее состояние сам, с определенной частотой. Для особо критичных секций мы можем вручную вызывать сохранение.
  • Можно отслеживать состояния документа. Если документ открыт для работы или находится в каких-то конфликтных ситуациях — например, мы осуществляем сохранение из разных точек, и где-то вдруг мы вызвали конфликт, — мы можем это отследить, обработать, поправить и уведомить пользователя правильной понятной ошибкой.
  • UIDocument позволяет читать и записывать документ асинхронно.
  • Он может создать стек Core data из коробки.
  • Есть встроенная функция хранения в iCloud и синхронизации с облаком. Это как раз то, к чему мы в будущем стремимся.
  • Поддержка версионности.
  • Используется Document based app парадигма — представление модели данных как контейнер для хранения этих данных. Если посмотреть на классическую модель MVC в документации Apple, можем увидеть, что Core data создана как раз для того, чтобы управлять этой моделью и помогать нам на более высоком уровне абстракции работать с данными. На уровне модели работаем, подключая UIManagedDocument со всем созданным стеком. А сам документ рассматриваем как контейнер, который хранит Core data и все данные из кэша (от экранов, пользователей). Плюс это могут быть картинки, видео, тексты — любая информация.

Мы же рассматриваем наше приложение, его запуск, авторизацию пользователей и все его данные как некий большой документ (файл), в котором хранится история нашего пользователя:



Процесс


Как мы проектировали архитектуру


Процесс проектирования у нас проходит в несколько этапов:

  1. Анализ технического задания.
  2. Отрисовка диаграммы UML-диаграмм. Мы используем в основном три типа UML-диаграмм: class diagram (диаграмма классов), flow chart (блок-схема), sequence diagram (диаграмма последовательностей). Это прямая обязанность senior-разрабочиков, но могут делать и разработчики с меньшим опытом. Это даже приветствуется, так как позволяет хорошо погрузиться в задачу и изучить все ее тонкости. Что помогает найти в ТЗ какие-то недоработки, а также структурировать всю информацию по задаче. И мы стараемся учитывать кросс-платформенность нашего приложения — мы тесно работаем с Android-командой, рисуя одну схему на две платформы и стараясь использовать основные общепринятые паттерны проектирования от «банды четырёх».
  3. Ревью архитектуры. Как правило, ревью и оценку проводит коллега из смежной команды.
  4. Реализация и тестирование на примере одного UI модуля.
  5. Масштабирование. Если тестирование проходит успешно, мы масштабируем архитектуру на все приложение.
  6. Рефакторинг. Чтобы проверить, не упустили ли мы что-нибудь.

Сейчас, после пяти месяцев разработки этого проекта, я могу показать весь наш процесс в три этапа: что было, как менялось и что получилось в результате.

Что было


Нашей отправной точкой была стандартная MVC архитектура — это связанные между собой слои:

  • UI слой, полностью программно сверстанный с использованием Objective C;
  • Класс презентации (модель);
  • Сервисный слой, где мы работаем с сетью.

Activity indicator был расположен в том месте схемы, где процесс получения данных чувствителен к скорости интернета — пользователь хочет быстрого результата, но вынужден смотреть на какие-то лоадеры, индикаторы и прочие сигналы. Это было нашими точками роста в user experience:



Переходный период


В переходный период мы должны были внедрить кэширование для экранов. Но так как приложение большое и содержит много legacy кода на Objective C, мы не можем просто взять и удалить все сервисы и модели, вставив Swift-код — мы должны учитывать, что параллельно с кэшированием у нас в разработке еще много других продуктовых задач.

Мы нашли безболезненный способ интегрироваться в текущий код максимально эффективно, ничего при этом не сломав, а первую итерацию провести максимально мягко. В левой части предыдущей схемы мы полностью убрали все, что связано с сетевыми запросами — по интерфейсу сервис теперь общается с DataSourceFacade. И сейчас это — фасад, с которым работает сервис. Он ждет от DataSource те данные, которые раньше получал от сети. А в самом DataSource скрыта логика по добыче этих данных.

В правой части схемы мы разбили получение данных на команды — паттерн Command нацелен выполнить какую-то базовую команду и получить результат. В случае iOS мы используем наследников NSOperation:



Каждая команда, которую вы здесь видите — это операция, в которой есть логическая единица ожидаемого действия. Это получение данных из БД (или сети) и сохранение этих данных в Core data. Например, главная задача AcquireCommand — не только вернуть фасаду источник данных, но и дать нам возможность разрабатывать код таким образом, чтобы получать данные через фасад. То есть взаимодействие с операциями идет через данный фасад.

А основная задача операций — передать данные DataSource для DataSourceFacade. Конечно, мы выстраиваем логику так, чтобы как можно быстрее показать данные пользователю. Как правило, внутри DataSourceFacade у нас есть операционная очередь, где мы запускаем наши NSOperations. В зависимости от настроенных условий мы можем принять решение, когда показывать данные из кэша, а когда — получать из сети. При первом запросе источника данных в фасаде мы идем в БД Core data, достаем оттуда через FetchCommand данные (если они там есть) и моментально возвращаем их пользователю.

Одновременно запускаем параллельный запрос данных через сеть, и когда этот запрос выполняется, то результат приходит в базу данных, сохраняется в ней, и после мы получаем update нашего DataSource. Этот update попадает уже в UI. Так мы минимизируем время ожидания данных, а пользователь, получая их мгновенно, не замечает разницы. Обновленные данные он получит сразу, как база данных получит ответ от сети.

Как стало


К такой более лаконичной схеме мы идем (и придем в итоге):



Сейчас из этого у нас есть:

  • UI слой,
  • фасад, через который мы предоставляем наш DataSource,
  • команда, которая этот DataSource вместе с updates возвращает.

Что такое DataSource и почему мы о нем так много говорим


DataSource — это объект, который предоставляет данные для слоя презентации и соответствует заранее определенному протоколу. А протокол должен быть подстроен под наш UI и предоставлять данные для нашего UI (неважно, для конкретного экрана или для группы экранов).

У DataSource, как правило, две основных обязанности:

  1. Предоставление данных для отображения в UI слое;
  2. Уведомление UI слоя об изменениях в данных и досылка необходимой пачки изменений для экрана, когда мы получаем обновление.

Мы у себя используем несколько вариантов DataSource, потому что у нас много Objective C legacy кода — то есть, мы не везде можем легко наш Swift’овый DataSource воткнуть. Еще мы пока не везде используем коллекции, но в будущем перепишем код именно для использования CollectionView экранов.

Пример одного из наших DataSource:



Это DataSource для коллекции (он так и называется CollectionDataSource) и это достаточно несложный класс с точки зрения интерфейса. Он принимает в себя коллекцию, настроенный fetchedResultsController и CellDequeueBlock. Где CellDequeueBlock — type alias, в котором мы описываем стратегию по созданию ячеек.

То есть мы создали DataSource и присвоили его коллекции, вызвав у fetchedResultsController performFetch, и дальше вся магия возложена на взаимодействие нашего класса DataSource, fetchedResultsController и возможность у делегата получать обновления из базы данных:



FetchedResultsController — сердце нашего DataSource. В документации Apple вы найдете много информации по работе с ним. Как правило, мы получаем все данные с его помощью — и новые данные, и данные, которые были обновлены или удалены. При этом мы параллельно запрашиваем данные из сети. Как только данные были получены и сохранились в БД, мы получили update у DataSource, и update пришел к нам в UI. То есть одним запросом мы и получаем данные, и показываем их в разных местах — классно, удобно, нативно!

И везде, где можно использовать уже готовые DataSource с таблицами или с коллекциями, мы это делаем:



В тех местах, где у нас много экранов и не используются таблицы и коллекции (а используется Objective C программная верстка), мы оцениваем, какие данные нам нужны для экрана, и через протокол описываем наш DataSource. После этого пишем фасад — как правило, это тоже публичный протокол Objective C, через который мы запрашиваем наш DataSource. А дальше уже идет вход в Swift’овый код.

Как только мы будем готовы перевести экран полностью в Swift-реализацию, достаточно будет убрать Objective C-обертку — и, благодаря кастомному DataSource, можно работать напрямую со Swift’овым протоколом.

Сейчас мы используем три основных варианта DataSources:
  1. TableViewDatasource + cell strategy (стратегия по созданию ячеек);
  2. CollectionViewDatasource + cell strategy (вариант с коллекциями);
  3. CustomDataSource — кастомный вариант. Его мы сейчас используем больше всего.


Результаты


После всех шагов по проектированию, реализации и взаимодействию с legacy кодом бизнес получил следующие улучшения:

  • Существенно повысилась скорость доставки данных до пользователя за счет кэширования. Это, наверное, очевидный и логичный результат.
  • Мы теперь на шаг ближе к парадигме offline first.
  • Настроены процессы архитектурного кроссплатформенного ревью внутри iOS & Android команд — все причастные к этому проекту разработчики владеют информацией и легко обмениваются опытом между командами.
  • Хорошую документацию к проекту за счет схем и описаний. Мы ее показываем нашим новым разработчикам, чтобы им было проще понять, как у нас проброшен мостик между legacy и новым кодом, и как работает сам процесс кэширования.
  • Мы уложились в сжатые спортивные сроки, и это — на живом проекте. У нас получилось, условно говоря, провести ремонт, никого не выселяя из офиса, при этом все продолжали работать, и даже не дышали строительной пылью.

Бонусом для нас стало то, что мы поняли, как работа с архитектурой и схемами может быть интересной и увлекательной (а это упрощает разработку). Да, мы потратили много времени на то, чтобы отрисовать и согласовать наши архитектурные подходы, но когда дошло до реализации, мы отмасштабировались очень быстро по всем экранам.

Наш путь в Offline first продолжается — нам нужно, чтобы не только кэширование было оффлайн, но и пользователь мог действовать без подключения к сети, с дальнейшей синхронизацией с сервером после появления интернета.

Ссылки


  • Document-based programming guide. Это довольно старый документ, Apple его уже не рекомендует использовать. Но я бы порекомендовал посмотреть хотя бы для дополнительного развития. Там очень много полезной информации.
  • Document-based WWDC: первый и второй
  • DataSources

Полный плейлист видео с конференции Apps Live 2020 опубликован здесь.
Докладчики рассказывали не только о разработке на двух основных платформах — Android и iOS, затронули и кроссплатформенную. Еще были выступления по юридической части, про профиты мобильной разработки в Китае, и многое другое.
Теги:
Хабы:
Всего голосов 6: ↑5 и ↓1 +4
Просмотры 1.8K
Комментарии Комментировать

Информация

Дата основания
Местоположение
Россия
Сайт
job.megafon.ru
Численность
свыше 10 000 человек
Дата регистрации