Открыть список
Как стать автором
Обновить
70,49
Рейтинг

Переходим В 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, затронули и кроссплатформенную. Еще были выступления по юридической части, про профиты мобильной разработки в Китае, и многое другое.
Теги:Разработка мобильных приложенийРазработка под Androidразработка под iosпрограммирование
Хабы: Блог компании МегаФон Блог компании Конференции Олега Бунина (Онтико) Разработка мобильных приложений Разработка под Android Разработка под MacOS
Всего голосов 6: ↑5 и ↓1 +4
Просмотры1.3K

Комментарии 0

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

Похожие публикации

Лучшие публикации за сутки

Информация

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

Блог на Хабре