NSUserDefaults is a key-value data storage in iOS, watchOS, tvOS, and macOS, designed for saving application settings and configurations. Data is stored in a plist file in the application sandbox and automatically syncs with iCloud via NSUbiquitousKeyValueStore. According to official documentation from Apple Developer, 2025, NSUserDefaults supports storing primitive types: String, Int, Bool, Float, Double, Data, Date, Array, and Dictionary. The class was renamed to UserDefaults starting with Swift 3, but its Objective-C name NSUserDefaults remains widely used in codebases and Apple documentation.
Key Takeaways
NSUserDefaults (UserDefaults in Swift) is Apple’s built-in mechanism for storing key-value pairs in plist format. It is available on all Apple platforms: iOS, iPadOS, watchOS, tvOS, and macOS. Its primary purpose is to save user preferences, interface state, first-launch flags, selected options, and other simple data that persists across application restarts.
Each iOS application has an isolated sandbox, and NSUserDefaults is stored in the Library/Preferences directory inside this sandbox in a file named after the Bundle Identifier. The plist file contains key-value pairs where the key is a string and the value is one of the supported types. The file size is not limited, but Apple recommends storing only settings in UserDefaults, not large amounts of data.
Starting with iOS 8, NSUserDefaults began supporting App Groups — shared storage between applications from the same developer and their extensions (widgets, watchOS companion apps). This uses the init?(suiteName:) initializer with an App Group identifier. This allows, for example, a Today widget to read settings from the main application without duplicating save logic.
Physically, NSUserDefaults is stored in a binary plist file at: {Sandbox}/Library/Preferences/com.example.myapp.plist. The file uses binary plist format (NSPropertyListBinaryFormat_v1_0) for compactness and read speed. On macOS, the file may be in XML format for compatibility. Unlike SharedPreferences on Android, UserDefaults plist files can contain nested structures via Dictionary and Array.
NSUserDefaults files are not encrypted by default. Data is stored in plain text and can be read with physical access to the device or through a backup. For storing sensitive data (passwords, tokens, encryption keys), Apple strongly recommends using Keychain, which automatically encrypts data at the operating system level.
NSUserDefaults operates on a memory caching principle with periodic disk synchronization. On the first access to the standard UserDefaults.standard instance, the system loads the plist file into RAM as a Dictionary. All subsequent reads are performed from memory. Writing also happens in memory first, with disk synchronization occurring periodically on a background thread.
Write operations use the set(_:forKey:) method, which accepts an optional Any? value. The value can be nil — used to remove a key. For immediate disk writes, the synchronize() method was previously used, but starting with iOS 7 and OS X 10.9 it is no longer required — the system automatically syncs data at regular intervals. Apple officially declared synchronize() redundant in its documentation.
NSUserDefaults uses a system of registers (domains) to organize value lookup. When an application requests a value by key, UserDefaults sequentially checks domains in a specific order: first NSArgumentDomain (command-line arguments), then the Application domain, then NSGlobalDomain (system settings), then Language-specific domains, and finally NSRegistrationDomain (default values registered via register(defaults:)).
import Foundation
// Standard UserDefaults instance
let defaults = UserDefaults.standard
// Writing values
defaults.set("Anna Petrova", forKey: "username")
defaults.set(28, forKey: "age")
defaults.set(true, forKey: "isLoggedIn")
// Registering default values
defaults.register(defaults: [
"theme": "system",
"fontSize": 14
])
// Reading with default value return
let theme = defaults.string(forKey: "theme") ?? "system"
let fontSize = defaults.integer(forKey: "fontSize")
The NSRegistrationDomain is a programmatic domain that exists only in memory and is not persisted to disk. It is used to set default values that apply until the application writes its own value to the Application domain. This allows creating a single configuration point for default settings that can be centrally changed during development.
NSUserDefaults provides a set of typed methods for reading and writing data: string(forKey:), integer(forKey:), bool(forKey:), float(forKey:), double(forKey:), data(forKey:), array(forKey:), dictionary(forKey:), and object(forKey:). Each read method has a corresponding write method set(_:forKey:) with automatic type inference for the stored value. The Swift version of UserDefaults uses strong typing, but the Objective-C version accepts and returns id.
| Read Method (Swift) | Data Type | Default Value |
|---|---|---|
| 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 |
The synchronize() method in NSUserDefaults forcibly writes all changes from memory to disk. In early versions of iOS, this method had to be called after every write to guarantee data persistence. Starting with iOS 7, the system automatically syncs UserDefaults on a background thread, and Apple officially declared synchronize() redundant. Calling this method does not cause an error but provides no additional persistence guarantees.
For monitoring changes, NSUserDefaults provides the UserDefaults.didChangeNotification notification and the KVO observation method addObserver(_:forKeyPath:options:context:). In SwiftUI, the @AppStorage Property Wrapper is available, which automatically syncs a value in UserDefaults with UI updates. @AppStorage supports the same types as UserDefaults and is the preferred way to work with settings in SwiftUI applications.
// Observing changes via KVO
class SettingsViewModel: NSObject {
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?
) {
guard let keyPath else { return }
print("Key changed: \(keyPath)")
}
}
// SwiftUI - AppStorage
struct SettingsView: View {
@AppStorage("theme") private var theme: String = "system"
var body: some View {
Picker("Theme", selection: $theme) {
Text("System").tag("system")
Text("Light").tag("light")
Text("Dark").tag("dark")
}
}
}
For working with App Groups (shared storage between an application and its extensions), use the UserDefaults(suiteName:) initializer with an App Group identifier. For example, “group.com.example.myapp”. Data written to this instance is accessible from the main application, widget, watchOS companion app, and other extensions belonging to the same App Group. Each suite instance is stored in a separate plist file.
Despite its convenience and simplicity, NSUserDefaults is not a universal storage solution for all data types on iOS. Depending on volume, criticality, and security requirements, Apple provides several alternatives, each optimized for a specific use case.
| Solution | When to Use | Limitations |
|---|---|---|
| NSUserDefaults | Interface settings and configuration | Not suitable for large data or secrets |
| Keychain | Passwords, tokens, encryption keys | More complex to use, slower |
| CoreData | Structured data with relationships | Overkill for 10–20 settings |
| FileManager | Documents, images, binary data | Requires manual file management |
| CloudKit | Cloud sync across devices | Requires iCloud account and network connection |
Keychain is Apple’s secure storage for confidential data. Unlike NSUserDefaults, all data in Keychain is encrypted at the operating system level using hardware encryption from Secure Enclave on compatible devices. Keychain automatically locks and unlocks with the device and supports access sharing between applications from the same developer via Keychain Access Groups.
The main drawback of Keychain is API complexity. To simply save a string, you need to create a SecItemAdd query specifying attributes: class (kSecClassGenericPassword), service (kSecAttrService), account (kSecAttrAccount), and the actual data (kSecValueData). To simplify working with Keychain, there are third-party wrappers such as KeychainAccess and SwiftKeychainWrapper that provide a convenient key-value interface similar to UserDefaults.
Let’s consider a practical example: saving and restoring onboarding state (welcome screens) in an iOS application using NSUserDefaults. On first launch, the user sees onboarding screens; after completing them, a flag is saved in UserDefaults. On subsequent launches, onboarding is skipped. For SwiftUI, @AppStorage is used; for UIKit, direct access to UserDefaults.standard.
Let’s create an OnboardingManager that encapsulates working with UserDefaults for storing onboarding status. The manager provides an isOnboardingCompleted property for checking state and a markOnboardingCompleted method for setting the flag. The storage key is extracted into a constant to prevent typos. For unit testing, the manager uses a UserDefaultsProtocol, allowing the real storage to be replaced with a 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)
}
}
// Usage in the application
let onboardingManager = OnboardingManager()
if !onboardingManager.isOnboardingCompleted {
showOnboarding()
} else {
showMainScreen()
}
For storing more complex settings, such as a structured Profile object, it is recommended to use the Codable protocol and JSONEncoder/JSONDecoder. The object is serialized to Data via JSONEncoder, saved via set(_:forKey:), and when reading, deserialized from Data back to the object via JSONDecoder. This approach allows storing complex structures in UserDefaults without losing type safety.
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)
}
}
// Usage
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")
It is important to remember that NSUserDefaults is not designed for storing large amounts of data. Apple recommends limiting stored data to a few dozen kilobytes. For storing large objects (images, documents, serialized models), use FileManager with the Documents directory or CoreData. Additionally, UserDefaults does not support data schema versioning — when the Codable model structure changes, old data may fail to deserialize, and this must be handled in the application code.
Frequently Asked Questions
Both are key-value stores, but NSUserDefaults supports more types (Data, Date, Array, Dictionary) and automatically syncs with iCloud. SharedPreferences stores data in XML, NSUserDefaults uses plist format. NSUserDefaults has a domain system with cascading lookup, while SharedPreferences uses a simple flat structure with file names.
No, NSUserDefaults stores data in plain text without encryption. For passwords, tokens, and encryption keys, use Keychain, which encrypts data at the Secure Enclave level. Keychain also supports access attributes such as biometric authentication (Face ID / Touch ID) before reading a secret.
For syncing between a single user’s devices, use NSUbiquitousKeyValueStore — the iCloud cloud key-value storage. Data written to this service on one device automatically appears on all other devices with the same iCloud account. Maximum capacity: 1 MB per application, 1024 keys.
To delete all data, call the removePersistentDomain(forName:) method with the application’s Bundle Identifier. To remove individual values, use removeObject(forKey:). For a complete settings reset: UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!). All deletions are applied immediately to the in-memory cache.
Apple does not set a hard limit on NSUserDefaults size, but it is recommended not to exceed 100 KB for the total volume of all stored data. For larger volumes, use CoreData or FileManager. When storing more than 1 MB of data, read performance at application launch may noticeably decrease.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also