Устройство хранения настроек в вашем приложении

Для разработчика есть несколько способов хранения переменных настроек в приложении, которые нам дает Apple изначально: NSUsedDefaults, NSUbiquitousKeyValueStore и конечно Keychain.

Начнем с простого NSUserDefaults - это класс в фреймворке Foundation, который предоставляет удобный механизм для хранения небольших наборов данных в iOS, macOS, watchOS и tvOS. Он появился в 2003 году и с тех пор изменился совсем несильно.

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

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

Мы можем создать NSUserDefaults, инициализируя своим id через метод initWithSuiteName и, таким образом, можем разделять данные между несколькими приложениями. Для этого нужно добавить одинаковый id в Capacity -> App Groups любого приложения, с которым мы хотим разделить хранилище.

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

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

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

Так же хочется сказать о секъюрности. В NSUserDefaults не рекомендуется хранить чувствительные данные пользователя, например пароли и иные данные покрайней мере без дополнительного слоя шифрования.

NSUbiquitousKeyValueStore - это брат близнец NSUserDefaults с одним отличием, он может разделять данные не только в рамках одного девайса пользователя, а и вообще везде где работает аккаунт пользователя. Например между айфоном и айпадом пользователя такое разделение будет работать отлично.

Все что говорилось о NSUserDefaults касается так же и NSUbiquitousKeyValueStore, за исключением того как его добавлять в проект. Если мы просто добавим NSUbiquitousKeyValueStore и начнем с ним работать, при первом жe обращении к хранилищу увидим сообщение в консоли "Trying to initialize NSUbiquitousKeyValueStore without a store identifier. Please specify a store identifier in your entitlements or initializer."

Для работы с этим классом необходимо включить в Capacity -> iCloud и поставить галочку напротив Key-Value, без них класс не будет сохранять или читать данные. После установки галочки класс сможет их не только читать и сохранять но и разделять в рамках iCloud аккаунта пользователя.

Разделение работает автоматически между устройствами пользователя, но в рамках одного приложения, точнее в рамках одной группы приложений. Тонкость здесь в том, что после включения галочки Key-Value, в файле Entitlement появляется автоматическая запись com.apple.developer.ubiquity-kvstore-identifier, содержащая id группы $(TeamIdentifierPrefix)$(CFBundleIdentifier) приложений.

$(TeamIdentifierPrefix) это id команды, например он может выглядеть так: 9T111111W8

$(CFBundleIdentifier) это Bundle идентификатор вашего приложения.

Например полный идентификатор может выглядеть так: 9T111111W8.myOrganization.myProducName

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

Теперь если мы хотим разделить данные для нескольких своих приложений, мы просто ставим во всех проектах в файле Entitlement, в записи com.apple.developer.ubiquity-kvstore-identifier, одинаковый идентификатор для всех ваших приложений, например: 9T111111W8.myOrganization.myProducName.

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

Поговорим о Keychain. Связка ключей, это возможность хранить данные пользователя секъюрно. Основным отличием от предыдущих классов является то, что если мы сохранили данные в Keychain то после удаления приложения данные не удалятся. Это является следствием того что в отличие от NSUsetDefaults, связка ключей является отдельной системной базой данных.

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

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

Для разделения данных в рамках одного приложения и одного аккаунта пользователя, достаточно установить этот же флаг как true. Тогда все записанные в связку ключей данные будут синхронизироваться через iCloud. Этот флаг по умолчанию включен. Так же требуется добавить Capacity -> Keychain Share.

Если же нам необходимо разделить данные между несколькими устройствами в рамках одной группы, то после добавления Capacity -> Keychain Share там же необходимо указать групповой идентификатор, например my.testingKeychain. Прочитать данные сможет любое другое ваше приложение знающее этот идентификатор.

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

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

Себе я довольно давно сделал удобную обертку, для работы со всеми этими классами в рамках одного интерфейса для записи и чтения данных. Я назвал его Settings, намекая для чего он служит. Этот класс проверен годами и работает например в приложении Mubert

В приложении Морзе

А также во многих других приложениях которые я когда либо делал.

Я исходил из интуитивно понятных метрик для обращения, например хранение данных локально в рамках одного приложения назвал (application), в рамках одного устройства (device), разделяемый режим для всех устройств и приложений (all), а для кейчейна есть (keychainLocal), (keychain) и (keychainShare).

Например так выглядит запись и чтение параметра на swift:

Settings.application["myKey"] = "Just string value" let value = Settings.application["myKey"]

Довольно просто, тоже самое для связки ключей:

Settings.keychain["myKey"] = "Just password value" let value = Settings.keychain["myKey"]

Буквально пару часов назад я закончил работу над оформлением этого простого но мощного инструмента в SPM пакет.

Было не просто, но мы справились 😁

Спасибо всем всем тем кто отметит этот удобный инструмент звездой на гитхабе.

Отдельное спасибо ребятам, кто помогал в нашей закрытой группе IT Elite в телеграм, ваша помощь бесценна.

Начать дискуссию