- PVSM.RU - https://www.pvsm.ru -
Данный текст представляет собой перевод статьи NSUserDefaults In Practice [1]. Автором оригинала является Дэвид Смит (David Smith). Перевод выполнен с любезного разрешения автора.
Комментарий с которого начинается заголовочный файл «NSUserDefaults.h» вполне хорошо описывает класс. Этим комментарием я и воспользуюсь, чтобы начать:
NSUserDefaults являются:
1) иерархическим
2) постоянным (персистентным)
3) межпроцессным
4) и в некоторых случаях распределенным
хранилищем вида ключ-значение. NSUserDefaults оптимизированы для хранения пользовательских настроек.
NSUserDefaults содержат список мест хранения данных, в котором они эти данные ищут. Этот список называется «список поиска». В «списке поиска» содержатся некоторые произвольные строки, называемые «идентификаторами набора»(suite identifier) или «идентификаторами домена». Когда поступает запрос NSUserDefaults проверяют каждый элемент в своем списке поиска, пока не найдут тот, который содержит ключ из запроса, или пока не пройдут весь список. Список включает:
Замечание: настройки «текущий хост + текущий пользователь» не реализованы на iOS, watchOS, и tvOS, а настройки «для любого пользователя» в основном ничего не дают приложениям на этих операционных системах.
Настройки сохраняемые в NSUserDefaults персистентны между перезагрузками и перезапусками приложений, если иное не указано.
Настройки могут быть доступны на чтение/запись из нескольких процессов одновременно (например, из приложения и его расширения).
На данный момент поддержка есть только в режиме Shared iPad для учащихся (программа Apple www.apple.com/education/it [2] — прим. перев.).
Данные сохраненные в NSUserDefaults могут быть сделаны распределенными («ubiqitous» — прим. перев.), т.е. синхронизирующимися между устройствами через облако. Распределенные «пользовательские настройки» автоматически передаются на все устройства залогиненные в один iCloud аккаунт. При чтении настроек (через вызов методов вида -*ForKey:) распределенные настройки проверяются перед локальными. Все операции с распределенными настройками асинхронные. Таким образом, если загрузка из iCloud не завершена, то зарегистрированные настройки могут быть возвращены, вместо распределенных. Распределенные настройки устанавливаются в конфигурационном файле настроек (Defaults Configuration File) приложения.
NSUserDefaults сохраняют объекты списков свойств (plist-файлов): NSString, NSData, NSNumber, NSDate, NSArray и NSDictionary — идентифицируемые по ключам типа NSString. Это подобно работе NSMutableDictionary.
NSUserDefaults предназначены для хранения относительно небольших объемов данных часто запрашиваемых и редко модифицируемых. Другие способы использования могут привести к медленной работе или большему потреблению памяти, чем более подходящие решения.
В CoreFoundation функции CFPreferences содержащие в названии «App» работают с теми же списками поиска, что и NSUserDefaults. Наблюдение NSUserDefaults с помощью механизма KVO (Key-Value Observing) возможно для любого сохраненного в них ключа. Когда наблюдение ведется за изменениями от других процессов или устройств, использование NSKeyValueObservingOptionPrior не влияет на поведение KVO.
В обычных обстоятельствах NSUserDefaults предельно просты.
Если есть настройка управляющая некоторой частью кода, вы просто вызываете подходящий геттер-метод (-objectForKey: или один из оберточных методов для конкретного типа).
Если обнаружилось, что вам требуется сделать что-то ещё для получения настройки, то следует сделать шаг назад и взвесить всё ещё раз:
Аналогично, когда пользователь изменяет некоторую настройку, вы просто вызываете -setObject:forKey: (или одну из его типо-специфичных оберток).
Если обнаружилось, что вам требуется что-то ещё, то, опять же, вероятно, это не нужно. Почти никогда не стоит вызывать -synchronize после установки значения (см. ниже Разделение настроек между программами [4]). И пользователи обычно не способны изменять настройки так быстро, чтобы «пакетирование» любого вида было бы полезно для производительности. Реальная запись на диск асинхронна, и NSUserDefaults производят слияние изменений в одну операцию записи автоматически.
Может показаться соблазнительным написать код наподобие:
- (void) applicationDidFinishLaunching:(NSApplication *)app {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:@"Something"]) {
[defaults setObject:initialValue forKey:@"Something"];
}
}
Но в долгосрочной перспективе он имеет скрытый изъян: если когда-нибудь возникнет желание изменить начальное значение, у вас не будет способа отличить значение установленное пользователем (которое он хотел бы сохранить) и начальное значение установленное вами (которое хотелось бы изменить). Кроме того, делать так отчасти медленно. Решение — использовать метод -registerDefaults:
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
@"Something" : initialValue
}];
Что имеет множество преимуществ:
Метод -registerDefaults: можно вызывать сколько угодно раз. И все пары ключ-значение из всех переданных ему словарей будут зарегистрированы. Это дает возможность держать регистрацию настроек рядом с кодом, который с ними работает.
Один сложный момент, который тем не менее часто встречается — это необходимость разделять настройки между несколькими запущенными процессами, например, между приложением и его расширением или между двумя и более приложениями (на macOS)
В старые (добрые/недобрые) времена, ещё до того как приложения стали помещать в «песочницы», всё было очень просто: используй [[NSUserDefaults alloc] initWithSuiteName:] с одинаковым именем в обоих процессах, и эти процессы станут разделять одни настройки. Терминологическое замечание: «домен» и «имя набора» используются как взаимозаменяемые. Оба термина обозначают просто произвольную строку, идентифицирующую хранилище настроек.
В мире «песочниц», в мире современных macOS и всех iOS NSUserDefaults изначально ограничены работой в «песочнице» вашего приложения. Если использовать -initWithSuiteName:, то получаешь всего лишь новое хранилище настроек, всё так же неразделяемое. Чтобы сделать его разделяемым нужны две вещи:
Если один из процессов устанавливает разделяемую настройку, а затем уведомляет другой процесс, чтобы тот прочитал её, то вы, возможно, в одной из тех очень редких ситуаций, когда вызов метода -synchronize может быть полезен. Это блокирующий метод. Он гарантирует, что после возврата из него чтение настройки любым другим процессом вернет новое, а не старое значение. Для приложений, исполняющихся на iOS 9.3 и последующих версий или на macOS Sierra и последующих версий, -synchronize не нужен (или не рекомендован) даже в описанной ситуации. Поскольку KVO-наблюдение за настройками теперь работает между процессами, то читающий процесс может просто наблюдать за изменением значения напрямую.
В результате приложения выполняющиеся на этих операционных системах в общем случае никогда не должны вызывать -synchronize.
Моя основная рекомендация: разделять как можно меньше настроек — просто потому, что код проще понимать и поддерживать, когда значения не изменяются извне.
Транзакции в NSUserDefaults не реализованы, поэтому нет способа гарантировать, что результаты нескольких изменений станут доступны для чтения все разом. Другое приложение может увидеть результаты первых изменений до того как последующие изменения будут завершены.
Распределенные (т.е. сохраняемые в iCloud) настройки сейчас поддерживаются только в режиме Shared iPad для обучения. Поэтому они вне сферы данного общего обсуждения. В настоящее время для распределенного хранения данных вне учебного режима следует использовать не NSUserDefaults, а NSUbiquitousKeyValueStore. Несколько нетривиальных моментов распределенных настроек упомянуты в разделе Ловушки и предостережения [6].
Несмотря на поставленную во главу угла простоту, остается масса способов создать себе проблемы.
NSUserDefaults значительно улучшились за годы своего существования. Список ниже актуален для iOS 10 и macOS Sierra, но должен быть более длинным для старых систем, и, скорее всего, станет короче в будущих.
Изменение значения для ключа «A» теряется, поскольку Поток 1 устанавливает весь словарь разом, и в этом словаре нет нового значения для ключа «A».
Мешок с подарками-сюрпризами полный реже используемыми возможностями NSUserDefaults. Может содержать пчёл.
В общем, производительность NSUserDefaults достаточно хороша, чтобы вам не стоило о ней волноваться. Однако, есть несколько моментов, о которых стоит знать, если проблема возникает (пожалуйста, используйте профилировщик, наподобие утилиты «Инструменты», для проверки!)
При первом чтении любой настройки весь набор загружается в память. Это может занять значительное время на медленных системах. Следствия из этого:
Даже если в домене нет настроек, всё равно возникают накладные расходы на обнаружение этого факта. Например, если у вас есть настройка «Включить отладочные логи», то обычно быстрее и экономичнее по памяти хранить её в стандартных настройках, чем в отдельном наборе «Логирование».
Чтение уже загруженных настроек происходит чрезвычайно быстро: порядка половины микросекунды на 2012 MacBook Pro. Определенные вещи могут инвалидировать кэш и потребовать повторной загрузки: если набор разделяем с другим процессом, то установка настройки в любом из процессов инвалидирует кэш в обоих. В более типичном случае не разделенных настроек чтение настройки после установки создаст небольшие накладные расходы, но не полную перестройку кэша. Выводы отсюда:
Многократная установка одного ключа, даже в разные значения, может быть значительно быстрее, чем установка многих различных ключей. Это позволяет NSUserDefaults быть быстрыми в случаях наподобие сохранения изменяемого в реальном времени размера окна.
Большая часть работы, вызываемой установкой настройки, является асинхронной. Но чтение может быть блокирующим, пока выполняется асинхронная запись. Вызов -synchronize тоже блокирующий. Перемежающиеся операции установки и чтения большого разделенного набора — худший случай для производительности настроек.
Установка значения в коллекции внутри настроек вызовет установку всей коллекции (по всей видимости, акцент на «и не изменившейся части коллекции тоже» — прим. перев.). Поддержка «частичной записи» работает только для ключей верхнего уровня.
Установка значения (в конечном счете она асинхронна и случается несколько позднее в другом процессе) запишет весь plist на диск, неважно сколь малым было изменение. Избегайте хранения больших объемов данных, особенно при частых изменениях.
Ужасный ужас программы «крестики-нолики», приводящий к печальному концу NSUserDefaults [8]
Автор: artemvkepke
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka-pod-ios/250186
Ссылки в тексте:
[1] NSUserDefaults In Practice: http://dscoder.com/defaults.html
[2] www.apple.com/education/it: http://www.apple.com/education/it/
[3] Регистрация значения по умолчанию: #register_default_value
[4] Разделение настроек между программами: #share_defaults
[5] здесь: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19
[6] Ловушки и предостережения: #caveeats
[7] документация по -addSuiteNamed:: https://developer.apple.com/reference/foundation/nsuserdefaults/1410294-addsuitenamed?language=objc
[8] Ужасный ужас программы «крестики-нолики», приводящий к печальному концу NSUserDefaults : http://dscoder.com/ImmutableTicTacToe.zip
[9] Источник: https://habrahabr.ru/post/324400/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.