NSUserDefaults es un almacenamiento de datos clave-valor en iOS, watchOS, tvOS y macOS, diseñado para guardar configuraciones y preferencias de la aplicación. Los datos se almacenan en un archivo plist dentro del sandbox de la aplicación y se sincronizan automáticamente con iCloud a través de NSUbiquitousKeyValueStore. Según la documentación oficial de Apple Developer, 2025, NSUserDefaults admite el almacenamiento de tipos primitivos: String, Int, Bool, Float, Double, Data, Date, Array y Dictionary. La clase fue renombrada a UserDefaults a partir de Swift 3, pero su nombre en Objective-C NSUserDefaults sigue siendo ampliamente utilizado en bases de código y documentación de Apple.
Puntos Clave
NSUserDefaults (UserDefaults en Swift) es el mecanismo integrado de Apple para almacenar pares clave-valor en formato plist. Está disponible en todas las plataformas Apple: iOS, iPadOS, watchOS, tvOS y macOS. Su propósito principal es guardar las preferencias del usuario, el estado de la interfaz, los indicadores de primer inicio, las opciones seleccionadas y otros datos simples que persisten entre reinicios de la aplicación.
Cada aplicación de iOS tiene un sandbox aislado, y NSUserDefaults se almacena en el directorio Library/Preferences dentro de este sandbox en un archivo con el nombre del Bundle Identifier. El archivo plist contiene pares clave-valor donde la clave es una cadena y el valor es uno de los tipos admitidos. El tamaño del archivo no está limitado, pero Apple recomienda almacenar solo configuraciones en UserDefaults, no grandes cantidades de datos.
A partir de iOS 8, NSUserDefaults comenzó a admitir App Groups, un almacenamiento compartido entre aplicaciones del mismo desarrollador y sus extensiones (widgets, aplicaciones complementarias de watchOS). Para ello se usa el inicializador init?(suiteName:) con un identificador de App Group. Esto permite, por ejemplo, que un widget en la pantalla Today lea las configuraciones de la aplicación principal sin duplicar la lógica de guardado.
Físicamente, NSUserDefaults se almacena en un archivo plist binario en: {Sandbox}/Library/Preferences/com.example.myapp.plist. El archivo utiliza el formato plist binario (NSPropertyListBinaryFormat_v1_0) para compacidad y velocidad de lectura. En macOS, el archivo puede estar en formato XML para compatibilidad. A diferencia de SharedPreferences en Android, los archivos plist de UserDefaults pueden contener estructuras anidadas mediante Dictionary y Array.
Los archivos de NSUserDefaults no están cifrados de forma predeterminada. Los datos se almacenan en texto plano y pueden leerse con acceso físico al dispositivo o mediante una copia de seguridad. Para almacenar datos sensibles (contraseñas, tokens, claves de cifrado), Apple recomienda encarecidamente usar Keychain, que cifra los datos automáticamente a nivel del sistema operativo.
NSUserDefaults funciona según el principio de almacenamiento en caché en memoria con sincronización periódica en disco. En el primer acceso a la instancia estándar UserDefaults.standard, el sistema carga el archivo plist en la RAM como un Dictionary. Todas las lecturas posteriores se realizan desde la memoria. La escritura también ocurre primero en memoria, y la sincronización en disco se realiza periódicamente en un hilo en segundo plano.
Las operaciones de escritura utilizan el método set(_:forKey:), que acepta un valor opcional de tipo Any?. El valor puede ser nil, utilizado para eliminar una clave. Para la escritura inmediata en disco, anteriormente se usaba el método synchronize(), pero a partir de iOS 7 y OS X 10.9 ya no es necesario: el sistema sincroniza los datos automáticamente en intervalos regulares. Apple declaró oficialmente synchronize() como redundante en su documentación.
NSUserDefaults utiliza un sistema de registros (dominios) para organizar la búsqueda de valores. Cuando una aplicación solicita un valor por clave, UserDefaults verifica secuencialmente los dominios en un orden específico: primero NSArgumentDomain (argumentos de línea de comandos), luego el dominio de la Aplicación, luego NSGlobalDomain (configuraciones del sistema), luego los dominios específicos del idioma y finalmente NSRegistrationDomain (valores predeterminados registrados mediante register(defaults:)).
import Foundation
// Instancia estándar de UserDefaults
let defaults = UserDefaults.standard
// Escritura de valores
defaults.set("Ana Petrova", forKey: "username")
defaults.set(28, forKey: "age")
defaults.set(true, forKey: "isLoggedIn")
// Registro de valores predeterminados
defaults.register(defaults: [
"theme": "system",
"fontSize": 14
])
// Lectura con devolución de valor predeterminado
let theme = defaults.string(forKey: "theme") ?? "system"
let fontSize = defaults.integer(forKey: "fontSize")
El dominio NSRegistrationDomain es un dominio programático que existe solo en la memoria y no se persiste en disco. Se utiliza para establecer valores predeterminados que aplican hasta que la aplicación escribe su propio valor en el dominio de la Aplicación. Esto permite crear un punto único de configuración para los valores predeterminados que se pueden cambiar centralizadamente durante el desarrollo.
NSUserDefaults proporciona un conjunto de métodos tipificados para leer y escribir datos: string(forKey:), integer(forKey:), bool(forKey:), float(forKey:), double(forKey:), data(forKey:), array(forKey:), dictionary(forKey:) y object(forKey:). Cada método de lectura tiene un método de escritura correspondiente set(_:forKey:) con determinación automática del tipo del valor almacenado. La versión Swift de UserDefaults usa tipado fuerte, pero la versión Objective-C acepta y devuelve id.
| Método de lectura (Swift) | Tipo de datos | Valor predeterminado |
|---|---|---|
| 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 |
El método synchronize() en NSUserDefaults fuerza la escritura de todos los cambios de la memoria al disco. En versiones anteriores de iOS, este método debía llamarse después de cada escritura para garantizar la persistencia de los datos. A partir de iOS 7, el sistema sincroniza UserDefaults automáticamente en un hilo en segundo plano, y Apple declaró oficialmente synchronize() como redundante. Llamar a este método no causa un error, pero no ofrece garantías adicionales de persistencia.
Para monitorear cambios, NSUserDefaults proporciona la notificación UserDefaults.didChangeNotification y el método de observación KVO addObserver(_:forKeyPath:options:context:). En SwiftUI, está disponible el Property Wrapper @AppStorage, que sincroniza automáticamente un valor en UserDefaults con la actualización de la UI. @AppStorage admite los mismos tipos que UserDefaults y es la forma preferida de trabajar con configuraciones en aplicaciones SwiftUI.
// Observación de cambios mediante KVO
class SettingsViewModel: NSObject {
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?
) {
guard let keyPath else { return }
print("Clave cambiada: \(keyPath)")
}
}
// SwiftUI - AppStorage
struct SettingsView: View {
@AppStorage("theme") private var theme: String = "system"
var body: some View {
Picker("Tema", selection: $theme) {
Text("Sistema").tag("system")
Text("Claro").tag("light")
Text("Oscuro").tag("dark")
}
}
}
Para trabajar con App Groups (almacenamiento compartido entre una aplicación y sus extensiones), se usa el inicializador UserDefaults(suiteName:) con un identificador de App Group. Por ejemplo, “group.com.example.myapp”. Los datos escritos en esta instancia son accesibles desde la aplicación principal, el widget, la aplicación complementaria de watchOS y otras extensiones que pertenecen al mismo App Group. Cada instancia de suite se almacena en un archivo plist separado.
A pesar de su conveniencia y simplicidad, NSUserDefaults no es una solución de almacenamiento universal para todos los tipos de datos en iOS. Dependiendo del volumen, la criticidad y los requisitos de seguridad, Apple proporciona varias alternativas, cada una optimizada para un caso de uso específico.
| Solución | Cuándo usar | Limitaciones |
|---|---|---|
| NSUserDefaults | Configuraciones de interfaz y preferencias | No apto para grandes datos o secretos |
| Keychain | Contraseñas, tokens, claves de cifrado | Más complejo de usar, más lento |
| CoreData | Datos estructurados con relaciones | Excesivo para 10–20 configuraciones |
| FileManager | Documentos, imágenes, datos binarios | Requiere gestión manual de archivos |
| CloudKit | Sincronización en la nube entre dispositivos | Requiere cuenta de iCloud y conexión de red |
Keychain es el almacenamiento seguro de Apple para datos confidenciales. A diferencia de NSUserDefaults, todos los datos en Keychain están cifrados a nivel del sistema operativo mediante el cifrado de hardware de Secure Enclave en dispositivos compatibles. Keychain se bloquea y desbloquea automáticamente con el dispositivo y admite el uso compartido de acceso entre aplicaciones del mismo desarrollador mediante Keychain Access Groups.
La principal desventaja de Keychain es la complejidad de su API. Para guardar simplemente una cadena, es necesario crear una consulta SecItemAdd especificando atributos: clase (kSecClassGenericPassword), servicio (kSecAttrService), cuenta (kSecAttrAccount) y los datos reales (kSecValueData). Para simplificar el trabajo con Keychain, existen envoltorios de terceros como KeychainAccess y SwiftKeychainWrapper que proporcionan una interfaz clave-valor conveniente similar a UserDefaults.
Consideremos un ejemplo práctico: guardar y restaurar el estado de onboarding (pantallas de bienvenida) en una aplicación iOS usando NSUserDefaults. En el primer inicio, el usuario ve las pantallas de onboarding; después de completarlas, se guarda un indicador en UserDefaults. En inicios posteriores, el onboarding se omite. Para SwiftUI se usa @AppStorage; para UIKit, acceso directo a UserDefaults.standard.
Creemos un OnboardingManager que encapsule el trabajo con UserDefaults para almacenar el estado del onboarding. El administrador proporciona una propiedad isOnboardingCompleted para verificar el estado y un método markOnboardingCompleted para establecer el indicador. La clave de almacenamiento se extrae en una constante para evitar errores tipográficos. Para pruebas unitarias, el administrador usa un protocolo UserDefaultsProtocol que permite reemplazar el almacenamiento real por un 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)
}
}
// Uso en la aplicación
let onboardingManager = OnboardingManager()
if !onboardingManager.isOnboardingCompleted {
showOnboarding()
} else {
showMainScreen()
}
Para almacenar configuraciones más complejas, como un objeto Profile estructurado, se recomienda usar el protocolo Codable y JSONEncoder/JSONDecoder. El objeto se serializa a Data mediante JSONEncoder, se guarda mediante set(_:forKey:), y al leer se deserializa de Data nuevamente al objeto mediante JSONDecoder. Este enfoque permite almacenar estructuras complejas en UserDefaults sin perder la seguridad de tipos.
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)
}
}
// Uso
let profile = UserProfile(name: "Ana", age: 28, preferences: ["theme": "dark"])
UserDefaults.standard.save(profile, forKey: "user_profile")
let loaded = UserDefaults.standard.load(UserProfile.self, forKey: "user_profile")
Es importante recordar que NSUserDefaults no está diseñado para almacenar grandes volúmenes de datos. Apple recomienda limitar los datos almacenados a unas pocas decenas de kilobytes. Para almacenar objetos grandes (imágenes, documentos, modelos serializados), use FileManager con el directorio Documents o CoreData. Además, UserDefaults no admite versionado de esquemas de datos: cuando cambia la estructura del modelo Codable, los datos antiguos pueden no deserializarse, y esto debe manejarse en el código de la aplicación.
Preguntas Frecuentes
Ambos son almacenes clave-valor, pero NSUserDefaults admite más tipos (Data, Date, Array, Dictionary) y se sincroniza automáticamente con iCloud. SharedPreferences almacena datos en XML, NSUserDefaults en formato plist. NSUserDefaults tiene un sistema de dominios con búsqueda en cascada, mientras que SharedPreferences usa una estructura plana simple con nombres de archivo.
No, NSUserDefaults almacena datos en texto plano sin cifrado. Para contraseñas, tokens y claves de cifrado, use Keychain, que cifra los datos a nivel de Secure Enclave. Keychain también admite atributos de acceso como autenticación biométrica (Face ID / Touch ID) antes de leer un secreto.
Para la sincronización entre dispositivos de un mismo usuario, use NSUbiquitousKeyValueStore, el almacén clave-valor en la nube de iCloud. Los datos escritos en este servicio en un dispositivo aparecen automáticamente en todos los demás dispositivos con la misma cuenta de iCloud. Capacidad máxima: 1 MB por aplicación, 1024 claves.
Para eliminar todos los datos, llame al método removePersistentDomain(forName:) con el Bundle Identifier de la aplicación. Para eliminar valores individuales, use removeObject(forKey:). Para un restablecimiento completo de la configuración: UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!). Todas las eliminaciones se aplican inmediatamente a la caché en memoria.
Apple no establece un límite estricto para el tamaño de NSUserDefaults, pero se recomienda no superar los 100 KB de volumen total de todos los datos almacenados. Para volúmenes mayores, use CoreData o FileManager. Al almacenar más de 1 MB de datos, el rendimiento de lectura al iniciar la aplicación puede disminuir notablemente.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.