Как стать автором
Обновить
147.68
hh.ru
HR Digital

Справочники в iOS: храним, обновляем, используем

Время на прочтение6 мин
Количество просмотров3.1K

Всем привет! Меня зовут Саша, и я iOS-разработчик в hh.ru

Страны, города, профобласти, языки, валюты – всё это названия справочников внутри нашего мобильного приложения. Они очень редко меняются, но используются повсюду, а поэтому обязаны быть актуальными и не должны тратить время пользователя и разработчика на свое обновление. В этой статье разберемся, как сделать работу со справочниками простой и удобной.

С чего всё начиналось

В нашем приложении справочники живут уже очень давно. До недавнего времени они представляли собой обычные текстовые файлы, в которых мы сохраняли ответы с сервера в JSON-формате. Таких данных было около 20 мегабайт, и для мобильного приложения это реально много. Подгрузка этих данных в приложении работала очень медленно, а обертка была написана на Objective-C. Короче говоря, страх и ужас легаси. 

Год назад мы начали работу над новым приложением для работодателей. Одним из блокеров для нас стало создание новой фичи для работы со справочниками. В существующем виде тащить ее в свежее и не оскверненное приложение совершенно не хотелось, поэтому справочники мы решили менять. 

Я походил по разработчикам, пообщался с ними о пожеланиях к новой фиче и составил список требований:

  • данные должны быть доступны как можно быстрее, прямо на старте приложения;

  • необходимо предусмотреть обновления справочников с сервера;

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

  • обеспечить быстрый поиск информации в этих справочниках;

  • работать всё это должно в отдельной фиче, которая будет шариться между нашими приложениями.

Поглядев на эти требования я понял, что вопроса «как хранить справочную информацию» попросту не существует – нам нужна база данных. Она обеспечит нам хранение информации справочников, быстрый поиск, возможность миграций и обновления.

Итого большую часть требований мы закрываем практически «из коробки». Оставалось понять, какую именно базу мы хотим использовать и как будем с ней работать. 

В итоге, у нас получилось несколько задач:

  • во-первых, нам нужно научиться поставлять данные вместе с приложением;

  • во-вторых, необходимо сделать работу с данными внутри нашего приложения удобной;

  • в-третьих, мы должны сделать так, чтобы данные обновлялись только при необходимости. 

В идеальном мире разработчику не нужно обновлять данные в бандле руками. В идеальном мире всё нужное обновление происходит либо по расписанию на стороне CI, либо во время создания релиз-кандидата для регресса. Запускаются какие-то скрипты и в репозиторий подкладывается свежая версия базы данных. Валидность этой базы обеспечивается интеграционными тестами. В общем, нам нужно было максимально автоматизировать поставку данных вместе с приложением и объединить генерацию словарей между iOS и Android. 

Базы данных и их дорогие товарищи

Взяв основные требования за основу, я собрал основные возможные варианты работы с базой данных в iOS. Вариантов получилось три: CoreData, Realm, SQLite. Дополнительно я посмотрел и менее популярные варианты вроде YAP, но быстро отказался от них. Сейчас кратко расскажу о плюсах и минусах каждого из основных вариантов.

CoreData. Плюсы очевидны: максимально нативно, добавление фреймворка не влияет на размер приложения, большинство наших разработчиков с ней уже работали. Плюс, в ней достаточно легко писать миграции. Минусы тоже понятны. БД можно генерировать только на mac или ios. Нельзя просто взять и сгенерировать sqlite-файл и подменить его. Нужно обязательно использовать версию моделей из вашего проекта. CoreData подходит только для iOS – никакой кроссплатформы. А еще она имеет достаточно громоздкое API. Да, Apple улучшило его, но все равно по удобству работы CoreData сильно проигрывает другим решениям.

Realm. Из плюсов: кроссплатформа, быстрый поиск по данным, простой и понятный код в проекте. Минусы тоже есть. Для генерации нужен проект с подключенным Realm – скриптом тут не обойтись. Это сторонняя библиотека и дополнительная зависимость, которая еще и увеличивает размер приложения от 5 до 14 мб. К тому же, наши Android-разработчики совершенно не хотели затаскивать к себе Realm. Соответственно, мы теряем плюс от кроссплатформы.

SQLite. Главный плюс – БД легко генерировать. Просто пиши скрипты, которые создают таблицы и вставляют данные. Это максимальная кроссплатформа: движок баз данных в Android тоже использует SQLite. Минусы и другие плюсы зависят от того, какую библиотеку вы выберете для работы с базой. Я рассматривал три варианта. 

Один из них – нативный, у него есть большой плюс в виде отсутствия зависимостей. Но есть и жирный минус – API вообще неудобное. Apple предлагает использовать указатель OpaquePointer для работы с базой.

GRDB.swift. Из плюсов: простая работа с данными, быстрая скорость работы, отличная поддержка автором и сообществом. Минусы: у нее нет поддержки Carthage, и это была принципиальная позиция автора библиотеки. Для нас это стало стоп-фактором, потому что мы активно работаем с Carthage и все наши основные библиотеки подключены именно через него. 

И, наконец, SQLite.swift. Плюсы: просто и удобно, есть поддержка комьюнити. У нас в проекте уже была эта библиотека в качестве зависимости от другой библиотеки. Теперь о минусах. На момент исследования последний коммит был в репозитории около двух лет назад. Но перед записью видеоверсии этой статьи я заходил на страничку библиотеки, и она обновлялась совсем недавно. Еще можно отметить то, что для миграций используется отдельный менеджер, но это общая проблема работы с sqlite.

Для каждого из пяти вариантов БД я создал маленький тестовый проект, который демонстрировал возможности взаимодействия: как работать с моделями, как сама БД может генерироваться и обновляться. А дальше мы с командой провели небольшое голосование, чтобы выделить абсолютного фаворита. Стоит отметить, что мы решали конкретную задачу по работе со справочниками, а не выбирали единственную базу данных для нашего приложения.

Оценивали работу с базой по 4 показателям: удобство генерации, удобство работы в коде, миграция данных и быстрый поиск в базе. Но действительно важных критерия было всего два – удобство генерации и удобство работы в коде, с учетом тех целей, которые мы ставили. В итоге первое место по генерации заслуженно занял SQLite, второе – CoreData, третье – Realm. В наших условиях сгенерировать базу на Realm было очень сложно.

А вот по удобству работы Realm оказался на первом месте, разумеется,с учетом тех сценариев работы, которые мы разработали по результатам нашего голосования. На втором месте оказался SQLite, но не нативный, а при использовании какой-либо библиотеки. И на третьем – CoreData. По остальным пунктам – хорошую скорость поиска дает любой вариант, а от миграции мы вообще отказались. Но об этом позже.

В итоге, мы выбрали самый простой вариант для генерации – SQLite и библиотеку SQLite.swift, которая уже была в проекте. Сама генерация SQlite-базы достаточно проста. Мы написали скрипт на Python, который скачивает данные с сервера и складывает их в таблички, причем каждый отдельный справочник вносит их в свою таблицу. Или как, например, в метро, в две таблицы: отдельно станции, отдельно линии. Наша итоговая база весит около семи мегабайт, но, прежде чем добавить ее в бандл, я сложил всё это дело в zip-архив и сжал до двух с половиной.

Справочники, тесты, данные

Я не стану слишком углубляться в работу со справочниками, поскольку здесь нет ничего интересного. В общих чертах, для каждой таблицы был написан свой провайдер. И в нем было два типа get-методов: для работы синхронно и асинхронно через Combine.

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

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

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

Теперь всё работает следующим образом:

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

  • Во-вторых, мы решили добавить в базу специальную таблицу с мета-данными. В ней мы храним дату последнего обновления по каждому справочнику, а также специальное значение ETAG. Через семь дней после даты последнего обновления приложение попробует скачать новые данные с учетом ETAG и поменяет дату в таблице на новую. Использование ETAG означает, что если на сервере данные не изменились, то в БД тоже ничего не поменяется, и не будет никакого лишнего траффика или нагрузки.

Метаданные о наших таблицах
Метаданные о наших таблицах

Итоги 

Благодаря переходу на SQLite для справочников мы избавились от файлов с JSON и уменьшили размер нашего приложения на 15 мб. Обновлять версию справочников в бандле стало куда проще, а работать с ними в коде – намного комфортнее. И мой главный посыл на сегодня: «Не бойтесь использовать базу SQLite в своих проектах. В некоторых сценариях она может дать ощутимую фору иным решениям».

На этом всё. Задавайте любые вопросы в комментариях к статье или в нашем телеграме. До новых встреч!

Теги:
Хабы:
+6
Комментарии0

Публикации

Информация

Сайт
hh.ru
Дата регистрации
Дата основания
Численность
501–1 000 человек
Местоположение
Россия