NSUserDefaults — це ключ-значення сховище даних у iOS, watchOS, tvOS та macOS, призначене для збереження налаштувань і конфігурацій застосунку. Дані зберігаються у plist-файлі в пісочниці застосунку та автоматично синхронізуються з iCloud через NSUbiquitousKeyValueStore. Згідно з офіційною документацією Apple Developer, 2025, NSUserDefaults підтримує зберігання примітивних типів: String, Int, Bool, Float, Double, Data, Date, Array та Dictionary. Клас перейменовано на UserDefaults починаючи з Swift 3, але його Objective-C ім’я NSUserDefaults залишається широко використовуваним у кодовій базі та документації Apple.
Головне
NSUserDefaults (UserDefaults у Swift) — це вбудований механізм Apple для зберігання пар ключ-значення у plist-форматі. Він доступний на всіх платформах Apple: iOS, iPadOS, watchOS, tvOS та macOS. Основне призначення — збереження користувацьких уподобань, стану інтерфейсу, прапорців першого запуску, вибраних опцій та інших простих даних, які переживають перезапуск застосунку.
Кожен застосунок iOS має ізольовану пісочницю, і NSUserDefaults зберігається в директорії Library/Preferences всередині цієї пісочниці у файлі з іменем Bundle Identifier. plist-файл містить пари ключ-значення, де ключем виступає рядок, а значенням — один із підтримуваних типів. Розмір файлу не обмежений, але Apple рекомендує зберігати в UserDefaults лише налаштування, а не великі обсяги даних.
Починаючи з iOS 8, NSUserDefaults почав підтримувати App Groups — спільне сховище між застосунками одного розробника та їхніми розширеннями (віджети, watchOS компаньйон-застосунки). Для цього використовується ініціалізатор init?(suiteName:) з ідентифікатором App Group. Це дозволяє, наприклад, віджету на екрані Today читати налаштування з основного застосунку без дублювання логіки збереження.
Фізично NSUserDefaults зберігається у бінарному plist-файлі за шляхом: {Sandbox}/Library/Preferences/com.example.myapp.plist. Файл використовує бінарний формат plist (NSPropertyListBinaryFormat_v1_0) для компактності та швидкості читання. У macOS файл може бути у XML-форматі для сумісності. На відміну від SharedPreferences на Android, plist-файли UserDefaults можуть містити вкладені структури через Dictionary та Array.
Файли NSUserDefaults не шифруються за замовчуванням. Дані зберігаються у відкритому вигляді та можуть бути прочитані при фізичному доступі до пристрою або через резервну копію. Для зберігання чутливих даних (паролі, токени, ключі шифрування) Apple категорично рекомендує використовувати Keychain, який автоматично шифрує дані на рівні операційної системи.
NSUserDefaults працює за принципом кешування в пам’яті з періодичною синхронізацією на диск. При першому зверненні до стандартного екземпляра UserDefaults.standard система завантажує plist-файл в оперативну пам’ять у вигляді Dictionary. Всі подальші читання виконуються з пам’яті. Запис також спочатку відбувається в пам’ять, а синхронізація на диск відбувається періодично у фоновому потоці.
Операції запису використовують метод set(_:forKey:), який приймає значення опціонального типу Any?. Значення може бути nil — для видалення ключа. Для негайного запису на диск раніше використовувався метод synchronize(), але починаючи з iOS 7 та OS X 10.9 він більше не потрібний — система автоматично синхронізує дані через регулярні проміжки часу. Apple офіційно оголосила synchronize() надлишковим у своїй документації.
NSUserDefaults використовує систему регістрів (доменів) для організації пошуку значень. Коли застосунок запитує значення за ключем, UserDefaults послідовно перевіряє домени в певному порядку: спочатку NSArgumentDomain (аргументи командного рядка), потім домен застосунку (Application), потім NSGlobalDomain (системні налаштування), потім домени, специфічні для мови, і нарешті NSRegistrationDomain (значення за замовчуванням, зареєстровані через register(defaults:)).
import Foundation
// Стандартний екземпляр UserDefaults
let defaults = UserDefaults.standard
// Запис значень
defaults.set("Ганна Петрова", forKey: "username")
defaults.set(28, forKey: "age")
defaults.set(true, forKey: "isLoggedIn")
// Реєстрація значень за замовчуванням
defaults.register(defaults: [
"theme": "system",
"fontSize": 14
])
// Читання з поверненням значення за замовчуванням
let theme = defaults.string(forKey: "theme") ?? "system"
let fontSize = defaults.integer(forKey: "fontSize")
Домен NSRegistrationDomain — це програмний домен, який існує лише в оперативній пам’яті та не зберігається на диск. Він використовується для завдання значень за замовчуванням, які діють, поки застосунок не запише своє значення в домен застосунку. Це дозволяє створити єдину точку конфігурації дефолтних налаштувань, які можна централізовано змінювати на етапі розробки.
NSUserDefaults надає набір типізованих методів для читання та запису даних: string(forKey:), integer(forKey:), bool(forKey:), float(forKey:), double(forKey:), data(forKey:), array(forKey:), dictionary(forKey:) та object(forKey:). Кожен метод читання має відповідний метод запису set(_:forKey:) з автоматичним визначенням типу значення, що зберігається. Swift-версія UserDefaults використовує строгу типізацію, але Objective-C версія приймає та повертає id.
| Метод читання (Swift) | Тип даних | Значення за замовчуванням |
|---|---|---|
| string(forKey:) | String? | nil |
| integer(forKey:) | Int | 0 |
| bool(forKey:) | Bool | false |
| float(forKey:) | Float | 0.0 |
| double(forKey:) | Double | 0.0 |
| data(forKey:) | Data? | nil |
Метод synchronize() у NSUserDefaults примусово записує всі зміни з пам’яті на диск. У ранніх версіях iOS цей метод необхідно було викликати після кожного запису для гарантії збереження даних. Починаючи з iOS 7, система автоматично синхронізує UserDefaults у фоновому потоці, і Apple офіційно оголосила synchronize() надлишковим. Виклик цього методу не призводить до помилки, але не дає жодних гарантій додаткової збереженості.
Для моніторингу змін NSUserDefaults надає сповіщення UserDefaults.didChangeNotification та метод KVO-спостереження addObserver(_:forKeyPath:options:context:). У SwiftUI доступний Property Wrapper @AppStorage, який автоматично синхронізує значення в UserDefaults з оновленням інтерфейсу. @AppStorage підтримує ті самі типи, що й UserDefaults, і є кращим способом роботи з налаштуваннями у SwiftUI-застосунках.
// Спостереження змін через KVO
class SettingsViewModel: NSObject {
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?
) {
guard let keyPath else { return }
print("Змінився ключ: \(keyPath)")
}
}
// SwiftUI - AppStorage
struct SettingsView: View {
@AppStorage("theme") private var theme: String = "system"
var body: some View {
Picker("Тема", selection: $theme) {
Text("Системна").tag("system")
Text("Світла").tag("light")
Text("Темна").tag("dark")
}
}
}
Для роботи з App Groups (спільне сховище між застосунком та його розширеннями) використовується ініціалізатор UserDefaults(suiteName:) з ідентифікатором App Group. Наприклад, «group.com.example.myapp». Дані, записані в цей екземпляр, доступні з основного застосунку, віджета, watchOS компаньйон-застосунку та інших розширень, що належать одному App Group. Кожен екземпляр suite зберігається в окремому plist-файлі.
Незважаючи на зручність та простоту, NSUserDefaults не є універсальним сховищем для всіх типів даних на iOS. Залежно від обсягу, критичності та вимог до безпеки, Apple надає кілька альтернатив, кожна з яких оптимізована під конкретний сценарій використання.
| Рішення | Коли використовувати | Обмеження |
|---|---|---|
| NSUserDefaults | Налаштування інтерфейсу та конфігурація | Не підходить для великих даних та секретів |
| Keychain | Паролі, токени, ключі шифрування | Складніший у використанні, повільніший |
| CoreData | Структуровані дані зі зв’язками | Надлишковий для 10–20 налаштувань |
| FileManager | Документи, зображення, бінарні дані | Вимагає ручного керування файлами |
| CloudKit | Хмарна синхронізація між пристроями | Вимагає iCloud облікового запису та мережевого з’єднання |
Keychain — це захищене сховище Apple для конфіденційних даних. На відміну від NSUserDefaults, всі дані в Keychain шифруються на рівні операційної системи з використанням апаратного шифрування Secure Enclave на сумісних пристроях. Keychain автоматично блокується та розблоковується разом із пристроєм і підтримує розподіл доступу між застосунками одного розробника через Keychain Access Groups.
Основний недолік Keychain — складність API. Для простого збереження рядка потрібно створити запит SecItemAdd із зазначенням атрибутів: класу (kSecClassGenericPassword), сервісу (kSecAttrService), облікового запису (kSecAttrAccount) та власне даних (kSecValueData). Для спрощення роботи з Keychain існують сторонні обгортки, такі як KeychainAccess та SwiftKeychainWrapper, які надають зручний ключ-значення інтерфейс, аналогічний UserDefaults.
Розглянемо практичний приклад: збереження та відновлення стану онбордингу (екранів привітання) у iOS-застосунку з використанням NSUserDefaults. При першому запуску користувач бачить екрани онбордингу, після проходження яких прапорець зберігається в UserDefaults. При подальших запусках онбординг пропускається. Для SwiftUI використовується @AppStorage, для UIKit — прямий доступ до UserDefaults.standard.
Створимо менеджер OnboardingManager, який інкапсулює роботу з UserDefaults для зберігання статусу онбордингу. Менеджер надає властивість isOnboardingCompleted для перевірки стану та метод markOnboardingCompleted для встановлення прапорця. Ключ для зберігання винесено в константу для запобігання помилкам. Для unit-тестування менеджер використовує протокол UserDefaultsProtocol, що дозволяє підмінити реальне сховище на MockUserDefaults.
class OnboardingManager {
private let defaults: UserDefaults
private let hasSeenKey = "has_seen_onboarding"
init(defaults: UserDefaults = .standard) {
self.defaults = defaults
}
var isOnboardingCompleted: Bool {
defaults.bool(forKey: hasSeenKey)
}
func markOnboardingCompleted() {
defaults.set(true, forKey: hasSeenKey)
}
func resetOnboarding() {
defaults.removeObject(forKey: hasSeenKey)
}
}
// Використання в застосунку
let onboardingManager = OnboardingManager()
if !onboardingManager.isOnboardingCompleted {
showOnboarding()
} else {
showMainScreen()
}
Для зберігання складніших налаштувань, таких як структурований об’єкт Profile, рекомендується використовувати протокол Codable та JSONEncoder/JSONDecoder. Об’єкт серіалізується в Data через JSONEncoder, зберігається через set(_:forKey:), і при читанні десеріалізується з Data назад в об’єкт через JSONDecoder. Цей підхід дозволяє зберігати в UserDefaults складні структури без втрати типобезпеки.
struct UserProfile: Codable {
let name: String
let age: Int
let preferences: [String: String]
}
extension UserDefaults {
func save<T: Codable>(_ value: T, forKey key: String) {
if let data = try? JSONEncoder().encode(value) {
set(data, forKey: key)
}
}
func load<T: Codable>(_ type: T.Type, forKey key: String) -> T? {
guard let data = data(forKey: key) else { return nil }
return try? JSONDecoder().decode(type, from: data)
}
}
// Використання
let profile = UserProfile(name: "Ганна", age: 28, preferences: ["theme": "dark"])
UserDefaults.standard.save(profile, forKey: "user_profile")
let loaded = UserDefaults.standard.load(UserProfile.self, forKey: "user_profile")
Важливо пам’ятати, що NSUserDefaults не призначений для зберігання великих обсягів даних. Apple рекомендує обмежувати розмір даних, що зберігаються, кількома десятками кілобайт. Для зберігання великих об’єктів (зображень, документів, серіалізованих моделей) слід використовувати FileManager з директорією Documents або CoreData. Крім того, UserDefaults не підтримує версіонування схеми даних — при зміні структури Codable-моделі старі дані можуть не десеріалізуватися, і це потрібно обробляти в коді застосунку.
Поширені запитання
Обидва є ключ-значення сховищами, але NSUserDefaults підтримує більше типів (Data, Date, Array, Dictionary) та автоматично синхронізується з iCloud. SharedPreferences зберігає дані в XML, NSUserDefaults — у plist-форматі. NSUserDefaults має систему доменів з каскадним пошуком, SharedPreferences використовує просту пласку структуру з іменами файлів.
Ні, NSUserDefaults зберігає дані у відкритому вигляді без шифрування. Для паролів, токенів та ключів шифрування використовуйте Keychain, який шифрує дані на рівні Secure Enclave. Keychain також підтримує атрибути доступу, такі як біометрична аутентифікація (Face ID / Touch ID) перед читанням секрету.
Для синхронізації між пристроями одного користувача використовуйте NSUbiquitousKeyValueStore — хмарне ключ-значення сховище iCloud. Дані, записані в цей сервіс на одному пристрої, автоматично з’являються на всіх інших пристроях того ж iCloud-облікового запису. Максимальний об’єм — 1 MB на застосунок, 1024 ключі.
Для видалення всіх даних викличте метод removePersistentDomain(forName:) з Bundle Identifier застосунку. Для видалення окремих значень використовуйте removeObject(forKey:). Для повного скидання налаштувань застосунку: UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!). Всі видалення застосовуються негайно до in-memory кешу.
Apple не встановлює жорсткого ліміту на розмір NSUserDefaults, але рекомендується не перевищувати 100 KB на загальний обсяг усіх даних, що зберігаються. Для більших обсягів використовуйте CoreData або FileManager. При зберіганні більше 1 MB даних продуктивність читання при запуску застосунку може помітно знизитися.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також