При разработке мобильного приложения важно учитывать архитектуру хранения данных, которая наилучшим образом соответствует потребностям приложения. Во многих случаях, особенно если приложение интенсивно использует данные, база данных является лучшим вариантом. У меня был большой опыт использования пакета SQLite.NET для Xamarin, в частности. Однако в некоторых случаях база данных не является лучшим вариантом для хранения данных. Например, простое хранилище ключ-значение часто является отличным решением для небольших фрагментов данных, таких как пользовательские настройки и настройки приложения, которые можно легко получить с помощью уникального ключа.

Введение в SimpleStorage

SimpleStorage — это кроссплатформенная реализация хранилища данных типа ключ-значение, использующая собственный API-интерфейс Preferences каждой платформы. Для iOS это означает NSUserDefaults, а для Android — SharedPreferences.

SimpleStorage предоставляет простой синхронный API. Это упрощает хранение произвольных пар данных «ключ-значение» как в iOS, так и в Android. SimpleStorage также использует метод двоичной сериализации, который позволяет сериализовать объекты на диск и десериализовать их при обратном считывании. Это упрощает хранение произвольных объектов, как встроенных (например, DateTime), так и пользовательских. Я рекомендую SimpleStorage как надежное, простое и легко интегрируемое решение для вашего проекта Xamarin.

Некоторые ключевые расширения

При использовании SimpleStorage в моем последнем проекте я заметил, что ему не хватает нескольких ключевых функций. Среди них были возможности:

  • Получить все ключи в заданной группе хранения
  • Получить все группы хранения
  • Получение полной группы хранения за один раз

В частности, невозможность получить все ключи была для меня препятствием. Я решил реализовать этот функционал как расширение для SimpleStorage.

Желаемые интерфейсы

Я хотел сохранить интерфейс, аналогичный реальному SimpleStorage, в своем классе-оболочке. Поскольку мы работаем на C#, я решил создать интерфейс IPersistentStorage для определения API этого модуля. Использование интерфейса также позволяет нам реализовать другую версию этих расширений для работы с модульными тестами (мы увидим это позже).

See the code in the full post.

Этот интерфейс похож на SimpleStorage API, за исключением нескольких отличий. Первое отличие — это свойство Groups. Этот API вернет IEnumerable всех имен групп хранения. Следующее отличие — метод KeysForGroup, возвращающий список ключей для данной группы хранения. Наконец, последнее большое отличие — API GetGroup, который возвращает словарь всех пар ключ-значение для данной группы хранения. Это действительно отличный метод полезности в некоторых ситуациях.

Реализация расширений

Основа реализации основана на частной структуре словаря, которая используется внутри для хранения групп и их ключей. Это реализовано как ConcurrentDictionary, чтобы обеспечить потокобезопасное манипулирование структурой. Ниже приведено определение класса и конструктор:

See the code in the full post.

Конструктор инициализирует словарь groupMappings на основе сохраненной схемы хранилища значений ключа. Схема обновляется по мере добавления и удаления ключей, что вы увидите чуть позже. Группа схемы хранит список групп хранения. Тогда у каждой группы хранения будет соответствующая группа хранения *Keys, которая содержит все ключи, связанные с этой группой. С помощью обеих этих предопределенных структур данных мы можем реконструировать наши группы хранения и соответствующие им ключи.

Теперь, когда мы определили базовую архитектуру, следующим шагом будет реализация отдельных интерфейсов. В некоторых случаях реализация обернутого метода проста: просто переход к эквиваленту SimpleStorage. Например, метод Get определен ниже:

See the code in the full post.

В API SimpleStorage вы можете выполнять операции Get/Set/Delete только в контексте объекта SimpleStorage (возвращенного из EditGroup). Этот API-оболочка просто позволяет вызывающей стороне передавать группу и ключ в одном и том же вызове метода, что, на мой взгляд, является хорошей абстракцией.

Однако сложная часть этой реализации заключается в отслеживании сохраняемых групп и ключей. Возьмем, например, метод Set:

See the code in the full post.

Вы заметите вызов метода TryAddGroupAndKey, который в основном принимает комбинацию группы и ключа и пытается добавить новую группу и/или ключ в хранилище. Метод TryAddGroupAndKey определен ниже:

See the code in the full post.

Помимо обновления словаря groupMappings, нам также необходимо обновить ключи для этой группы. Если это новая группа, то нам нужно добавить ее в хранилище схем. Вы можете представить очень похожую (но противоположную) реализацию для TryRemoveGroupAndKey, которая используется в методе Delete. Каждый раз, когда словарь изменяется, мы соответствующим образом обновляем сохраняемую схему, что позволяет нам всегда поддерживать правильную схему хранения. Всю реализацию PersistentStorage можно найти здесь.

Что насчет тестов?

Я также написал реализацию IPersistentStorage для тестирования под названием TestStorage. Вы также можете найти эту реализацию здесь. Эта реализация вообще не поддерживается SimpleStorage, но она хранит локальный словарь групп, ключей и значений в оперативной памяти. Он имеет все те же интерфейсы и функции, что и настоящая версия, но обычно вам не нужно беспокоиться о фактическом сохранении данных во время модульного тестирования.

Ну это все! Я весело провел время, расширяя возможности SimpleStorage, и я надеюсь, что вы узнали кое-что о том, как хранить простые данные пары «ключ-значение» в Xamarin/C#.

Первоначально опубликовано на spin.atomicobject.com 12 сентября 2016 г.