NSUserDefaults to magazyn danych klucz-wartość w iOS, watchOS, tvOS i macOS, przeznaczony do zapisywania ustawień i konfiguracji aplikacji. Dane przechowywane są w pliku plist w piaskownicy aplikacji i automatycznie synchronizowane z iCloud przez NSUbiquitousKeyValueStore. Według oficjalnej dokumentacji Apple Developer, 2025, NSUserDefaults obsługuje przechowywanie typów prymitywnych: String, Int, Bool, Float, Double, Data, Date, Array i Dictionary. Klasa została przemianowana na UserDefaults od Swift 3, ale jej Objective-C nazwa NSUserDefaults pozostaje szeroko używana w bazie kodowej i dokumentacji Apple.
Najważniejsze
NSUserDefaults (UserDefaults w Swift) to wbudowany mechanizm Apple do przechowywania par klucz-wartość w formacie plist. Jest dostępny na wszystkich platformach Apple: iOS, iPadOS, watchOS, tvOS i macOS. Główne przeznaczenie — zapisywanie preferencji użytkownika, stanu interfejsu, flag first-launch, wybranych opcji i innych prostych danych, które przetrwają restart aplikacji.
Każda aplikacja iOS ma izolowaną piaskownicę, a NSUserDefaults przechowywany jest w katalogu Library/Preferences wewnątrz tej piaskownicy w pliku o nazwie Bundle Identifier. Plik plist zawiera pary klucz-wartość, gdzie kluczem jest ciąg znaków, a wartością jeden z obsługiwanych typów. Rozmiar pliku nie jest ograniczony, ale Apple zaleca przechowywanie w UserDefaults tylko ustawień, a nie dużych ilości danych.
Od iOS 8, NSUserDefaults obsługuje App Groups — współdzielone przechowywanie między aplikacjami tego samego dewelopera i ich rozszerzeniami (widgety, aplikacje towarzyszące watchOS). Służy do tego inicjalizator init?(suiteName:) z identyfikatorem App Group. Pozwala to na przykład widżetowi na ekranie Today odczytywać ustawienia z głównej aplikacji bez duplikowania logiki zapisywania.
Fizycznie NSUserDefaults przechowywany jest w binarnym pliku plist pod ścieżką: {Sandbox}/Library/Preferences/com.example.myapp.plist. Plik używa binarnego formatu plist (NSPropertyListBinaryFormat_v1_0) dla kompaktowości i szybkości odczytu. W macOS plik może być w formacie XML dla zgodności. W przeciwieństwie do SharedPreferences na Androidzie, pliki plist UserDefaults mogą zawierać zagnieżdżone struktury poprzez Dictionary i Array.
Pliki NSUserDefaults nie są domyślnie szyfrowane. Dane przechowywane są w otwartej formie i mogą być odczytane przy fizycznym dostępie do urządzenia lub przez kopię zapasową. Do przechowywania wrażliwych danych (hasła, tokeny, klucze szyfrowania) Apple kategorycznie zaleca używanie Keychain, który automatycznie szyfruje dane na poziomie systemu operacyjnego.
NSUserDefaults działa na zasadzie buforowania w pamięci z okresową synchronizacją na dysk. Przy pierwszym odwołaniu do standardowego egzemplarza UserDefaults.standard system ładuje plik plist do pamięci RAM w formie Dictionary. Wszystkie kolejne odczyty wykonywane są z pamięci. Zapis również najpierw odbywa się w pamięci, a synchronizacja na dysk następuje okresowo w tle.
Operacje zapisu używają metody set(_:forKey:), która przyjmuje wartość opcjonalnego typu Any?. Wartość może być nil — do usunięcia klucza. Do natychmiastowego zapisu na dysk wcześniej używano metody synchronize(), ale od iOS 7 i OS X 10.9 nie jest już wymagana — system automatycznie synchronizuje dane w regularnych odstępach czasu. Apple oficjalnie uznało synchronize() za zbędną w swojej dokumentacji.
NSUserDefaults używa systemu rejestrów (domen) do organizacji wyszukiwania wartości. Gdy aplikacja żąda wartości po kluczu, UserDefaults kolejno sprawdza domeny w określonej kolejności: najpierw NSArgumentDomain (argumenty wiersza poleceń), następnie domenę aplikacji (Application), potem NSGlobalDomain (ustawienia systemowe), następnie domeny specyficzne dla języka i na końcu NSRegistrationDomain (wartości domyślne zarejestrowane przez register(defaults:)).
import Foundation
// Standardowy egzemplarz UserDefaults
let defaults = UserDefaults.standard
// Zapisywanie wartości
defaults.set("Anna Pietrowa", forKey: "username")
defaults.set(28, forKey: "age")
defaults.set(true, forKey: "isLoggedIn")
// Rejestracja wartości domyślnych
defaults.register(defaults: [
"theme": "system",
"fontSize": 14
])
// Odczyt z zwróceniem wartości domyślnej
let theme = defaults.string(forKey: "theme") ?? "system"
let fontSize = defaults.integer(forKey: "fontSize")
Domena NSRegistrationDomain to domena programowa, która istnieje tylko w pamięci RAM i nie jest zapisywana na dysk. Służy do ustawiania wartości domyślnych, które działają, dopóki aplikacja nie zapisze swojej wartości w domenie aplikacji. Pozwala to na utworzenie jednolitego punktu konfiguracji domyślnych ustawień, które można centralnie zmieniać na etapie rozwoju.
NSUserDefaults udostępnia zestaw typowanych metod do odczytu i zapisu danych: string(forKey:), integer(forKey:), bool(forKey:), float(forKey:), double(forKey:), data(forKey:), array(forKey:), dictionary(forKey:) i object(forKey:). Każda metoda odczytu ma odpowiadającą jej metodę zapisu set(_:forKey:) z automatycznym określeniem typu przechowywanej wartości. Swiftowa wersja UserDefaults używa ścisłej typizacji, ale wersja Objective-C przyjmuje i zwraca id.
| Metoda odczytu (Swift) | Typ danych | Wartość domyślna |
|---|---|---|
| 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 |
Metoda synchronize() w NSUserDefaults wymusza zapis wszystkich zmian z pamięci na dysk. We wczesnych wersjach iOS tę metodę należało wywoływać po każdym zapisie dla gwarancji zachowania danych. Od iOS 7 system automatycznie synchronizuje UserDefaults w tle, a Apple oficjalnie uznało synchronize() za zbędną. Wywołanie tej metody nie powoduje błędu, ale nie daje żadnych gwarancji dodatkowego zachowania danych.
Do monitorowania zmian NSUserDefaults udostępnia powiadomienie UserDefaults.didChangeNotification oraz metodę obserwacji KVO addObserver(_:forKeyPath:options:context:). W SwiftUI dostępny jest Property Wrapper @AppStorage, który automatycznie synchronizuje wartość w UserDefaults z aktualizacją UI. @AppStorage obsługuje te same typy co UserDefaults i jest preferowanym sposobem pracy z ustawieniami w aplikacjach SwiftUI.
// Obserwowanie zmian przez KVO
class SettingsViewModel: NSObject {
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?
) {
guard let keyPath else { return }
print("Zmienił się klucz: \(keyPath)")
}
}
// SwiftUI - AppStorage
struct SettingsView: View {
@AppStorage("theme") private var theme: String = "system"
var body: some View {
Picker("Motyw", selection: $theme) {
Text("Systemowy").tag("system")
Text("Jasny").tag("light")
Text("Ciemny").tag("dark")
}
}
}
Do pracy z App Groups (współdzielone przechowywanie między aplikacją a rozszerzeniami) używa się inicjalizatora UserDefaults(suiteName:) z identyfikatorem App Group. Na przykład "group.com.example.myapp". Dane zapisane w tym egzemplarzu są dostępne z głównej aplikacji, widżetu, aplikacji towarzyszącej watchOS i innych rozszerzeń należących do jednej App Group. Każdy egzemplarz suite przechowywany jest w osobnym pliku plist.
Pomimo wygody i prostoty, NSUserDefaults nie jest uniwersalnym magazynem dla wszystkich typów danych na iOS. W zależności od objętości, krytyczności i wymagań bezpieczeństwa, Apple udostępnia kilka alternatyw, z których każda jest zoptymalizowana pod konkretny scenariusz użycia.
| Rozwiązanie | Kiedy używać | Ograniczenia |
|---|---|---|
| NSUserDefaults | Ustawienia interfejsu i konfiguracja | Nie nadaje się do dużych danych i tajemnic |
| Keychain | Hasła, tokeny, klucze szyfrowania | Bardziej skomplikowane w użyciu, wolniejsze |
| CoreData | Dane strukturalne z relacjami | Zbędne dla 10-20 ustawień |
| FileManager | Dokumenty, obrazy, dane binarne | Wymaga ręcznego zarządzania plikami |
| CloudKit | Synchronizacja chmurowa między urządzeniami | Wymaga konta iCloud i połączenia sieciowego |
Keychain to chronione przechowywanie Apple dla poufnych danych. W przeciwieństwie do NSUserDefaults, wszystkie dane w Keychain są szyfrowane na poziomie systemu operacyjnego z wykorzystaniem sprzętowego szyfrowania Secure Enclave na zgodnych urządzeniach. Keychain automatycznie blokuje się i odblokowuje wraz z urządzeniem oraz obsługuje podział dostępu między aplikacjami tego samego dewelopera przez Keychain Access Groups.
Główną wadą Keychain jest złożoność API. Do prostego zapisania ciągu znaków należy utworzyć żądanie SecItemAdd z określeniem atrybutów: klasy (kSecClassGenericPassword), serwisu (kSecAttrService), konta (kSecAttrAccount) i właściwych danych (kSecValueData). Do uproszczenia pracy z Keychain istnieją zewnętrzne nakładki, takie jak KeychainAccess i SwiftKeychainWrapper, które udostępniają wygodny interfejs klucz-wartość, podobny do UserDefaults.
Rozważmy praktyczny przykład: zapisywanie i przywracanie stanu onboardingu (ekranów powitalnych) w aplikacji iOS z użyciem NSUserDefaults. Przy pierwszym uruchomieniu użytkownik widzi ekrany onboardingu, po przejściu których flaga jest zapisywana w UserDefaults. Przy kolejnych uruchomieniach onboarding jest pomijany. Dla SwiftUI używa się @AppStorage, dla UIKit — bezpośredniego dostępu do UserDefaults.standard.
Stwórzmy menedżer OnboardingManager, który enkapsuluje pracę z UserDefaults do przechowywania statusu onboardingu. Menedżer udostępnia właściwość isOnboardingCompleted do sprawdzania stanu oraz metodę markOnboardingCompleted do ustawienia flagi. Klucz do przechowywania został wyniesiony do stałej, aby zapobiec literówkom. Do testów jednostkowych menedżer używa protokołu UserDefaultsProtocol, co pozwala zastąpić rzeczywiste przechowywanie na 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)
}
}
// Użycie w aplikacji
let onboardingManager = OnboardingManager()
if !onboardingManager.isOnboardingCompleted {
showOnboarding()
} else {
showMainScreen()
}
Do przechowywania bardziej złożonych ustawień, takich jak strukturalny obiekt Profile, zaleca się używanie protokołu Codable oraz JSONEncoder/JSONDecoder. Obiekt jest serializowany do Data przez JSONEncoder, zapisywany przez set(_:forKey:), a przy odczycie deserializowany z Data z powrotem do obiektu przez JSONDecoder. To podejście pozwala przechowywać w UserDefaults złożone struktury bez utraty bezpieczeństwa typów.
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)
}
}
// Użycie
let profile = UserProfile(name: "Anna", age: 28, preferences: ["theme": "dark"])
UserDefaults.standard.save(profile, forKey: "user_profile")
let loaded = UserDefaults.standard.load(UserProfile.self, forKey: "user_profile")
Ważne jest, aby pamiętać, że NSUserDefaults nie jest przeznaczony do przechowywania dużych ilości danych. Apple zaleca ograniczanie rozmiaru przechowywanych danych do kilkudziesięciu kilobajtów. Do przechowywania dużych obiektów (obrazów, dokumentów, serializowanych modeli) należy używać FileManager z katalogiem Documents lub CoreData. Ponadto UserDefaults nie obsługuje wersjonowania schematu danych — przy zmianie struktury modelu Codable stare dane mogą nie zostać zdeserializowane i należy to obsłużyć w kodzie aplikacji.
Często zadawane pytania
Oba są magazynami klucz-wartość, ale NSUserDefaults obsługuje więcej typów (Data, Date, Array, Dictionary) i automatycznie synchronizuje się z iCloud. SharedPreferences przechowuje dane w XML, NSUserDefaults — w formacie plist. NSUserDefaults ma system domen z kaskadowym wyszukiwaniem, SharedPreferences używa prostej płaskiej struktury z nazwami plików.
Nie, NSUserDefaults przechowuje dane w otwartej formie bez szyfrowania. Do haseł, tokenów i kluczy szyfrowania używaj Keychain, który szyfruje dane na poziomie Secure Enclave. Keychain obsługuje również atrybuty dostępu, takie jak uwierzytelnianie biometryczne (Face ID / Touch ID) przed odczytem sekretu.
Do synchronizacji między urządzeniami tego samego użytkownika używaj NSUbiquitousKeyValueStore — chmurowego magazynu klucz-wartość iCloud. Dane zapisane w tym serwisie na jednym urządzeniu automatycznie pojawiają się na wszystkich innych urządzeniach tego samego konta iCloud. Maksymalna objętość — 1 MB na aplikację, 1024 klucze.
Aby usunąć wszystkie dane, wywołaj metodę removePersistentDomain(forName:) z Bundle Identifier aplikacji. Do usunięcia pojedynczych wartości użyj removeObject(forKey:). Do całkowitego resetu ustawień aplikacji: UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!). Wszystkie usunięcia są natychmiast stosowane do pamięci podręcznej in-memory.
Apple nie ustanawia ścisłego limitu rozmiaru NSUserDefaults, ale zaleca się nie przekraczać 100 KB całkowitej objętości wszystkich przechowywanych danych. Dla większych objętości używaj CoreData lub FileManager. Przy przechowywaniu ponad 1 MB danych wydajność odczytu przy uruchomieniu aplikacji może zauważalnie spaść.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również