[Разработка мобильных приложений, Разработка под Android, Разработка под MacOS] Переходим В OFFLINE FIRST с использованием Core Data и Managed Document(s)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
10-Дек-2020 15:31

Придя в компанию МегаФон как 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 и все данные из кэша (от экранов, пользователей). Плюс это могут быть картинки, видео, тексты — любая информация.

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

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

Сейчас, после пяти месяцев разработки этого проекта, я могу показать весь наш процесс в три этапа: что было, как менялось и что получилось в результате.
Что было
Нашей отправной точкой была стандартная 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, как правило, две основных обязанности:
  • Предоставление данных для отображения в UI слое;
  • Уведомление 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:
  • TableViewDatasource + cell strategy (стратегия по созданию ячеек);
  • CollectionViewDatasource + cell strategy (вариант с коллекциями);
  • CustomDataSource — кастомный вариант. Его мы сейчас используем больше всего.

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

Бонусом для нас стало то, что мы поняли, как работа с архитектурой и схемами может быть интересной и увлекательной (а это упрощает разработку). Да, мы потратили много времени на то, чтобы отрисовать и согласовать наши архитектурные подходы, но когда дошло до реализации, мы отмасштабировались очень быстро по всем экранам.
Наш путь в Offline first продолжается — нам нужно, чтобы не только кэширование было оффлайн, но и пользователь мог действовать без подключения к сети, с дальнейшей синхронизацией с сервером после появления интернета.
Ссылки
  • Document-based programming guide. Это довольно старый документ, Apple его уже не рекомендует использовать. Но я бы порекомендовал посмотреть хотя бы для дополнительного развития. Там очень много полезной информации.
  • Document-based WWDC: первый и второй
  • DataSources

Плейлист видео с конференции Apps Live 2020 мы опубликовали здесь.
Докладчики рассказали не только о разработке на двух основных платформах — Android и iOS, затронули и кроссплатформенную. Еще были выступления по юридической части, про профиты мобильной разработки в Китае, и многое другое.

===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_razrabotka_pod_macos (Разработка под MacOS), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_razrabotka_pod_ios (разработка под ios), #_programmirovanie (программирование), #_blog_kompanii_megafon (
Блог компании МегаФон
)
, #_blog_kompanii_konferentsii_olega_bunina_(ontiko) (
Блог компании Конференции Олега Бунина (Онтико)
)
, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
, #_razrabotka_pod_android (
Разработка под Android
)
, #_razrabotka_pod_macos (
Разработка под MacOS
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 11:16
Часовой пояс: UTC + 5