NSUserDefaults는 iOS, watchOS, tvOS 및 macOS에서 키-값 데이터 저장소로, 애플리케이션 설정 및 구성을 저장하도록 설계되었습니다. 데이터는 애플리케이션 샌드박스 내의 plist 파일에 저장되며 NSUbiquitousKeyValueStore를 통해 자동으로 iCloud와 동기화됩니다. 공식 문서에 따르면 Apple Developer, 2025, NSUserDefaults는 기본 유형의 저장을 지원합니다: String, Int, Bool, Float, Double, Data, Date, Array 및 Dictionary. 이 클래스는 Swift 3부터 UserDefaults로 이름이 변경되었지만, Objective-C 이름인 NSUserDefaults는 코드베이스와 Apple 문서에서 계속 널리 사용되고 있습니다.
핵심 요점
NSUserDefaults(Swift에서는 UserDefaults)는 plist 형식으로 키-값 쌍을 저장하기 위한 Apple의 내장 메커니즘입니다. iOS, iPadOS, watchOS, tvOS 및 macOS의 모든 Apple 플랫폼에서 사용할 수 있습니다. 주요 목적은 사용자 기본 설정, 인터페이스 상태, 첫 실행 플래그, 선택한 옵션 및 애플리케이션 재시작 후에도 유지되는 기타 간단한 데이터를 저장하는 것입니다.
각 iOS 애플리케이션에는 격리된 샌드박스가 있으며, NSUserDefaults는 이 샌드박스 내의 Library/Preferences 디렉토리에 Bundle Identifier라는 이름의 파일로 저장됩니다. plist 파일에는 키가 문자열이고 값이 지원되는 유형 중 하나인 키-값 쌍이 포함됩니다. 파일 크기에 제한은 없지만, Apple은 UserDefaults에 설정만 저장하고 대량의 데이터는 저장하지 않도록 권장합니다.
iOS 8부터 NSUserDefaults는 App Groups을 지원하기 시작했습니다. 이는 동일한 개발자의 애플리케이션과 그 확장(위젯, watchOS 컴패니언 앱) 간의 공유 저장소입니다. 이를 위해 App Group 식별자와 함께 init?(suiteName:) 초기화 프로그램을 사용합니다. 예를 들어, Today 위젯이 저장 로직을 복제하지 않고 기본 애플리케이션에서 설정을 읽을 수 있습니다.
물리적으로 NSUserDefaults는 바이너리 plist 파일에 저장됩니다: {Sandbox}/Library/Preferences/com.example.myapp.plist. 파일은 간결성과 읽기 속도를 위해 바이너리 plist 형식(NSPropertyListBinaryFormat_v1_0)을 사용합니다. macOS에서는 호환성을 위해 파일이 XML 형식일 수 있습니다. Android의 SharedPreferences와 달리 UserDefaults의 plist 파일은 Dictionary 및 Array를 통해 중첩된 구조를 포함할 수 있습니다.
NSUserDefaults 파일은 기본적으로 암호화되지 않습니다. 데이터는 일반 텍스트로 저장되며 기기에 대한 물리적 접근이나 백업을 통해 읽을 수 있습니다. 민감한 데이터(비밀번호, 토큰, 암호화 키)를 저장하기 위해 Apple은 운영 체제 수준에서 데이터를 자동으로 암호화하는 Keychain 사용을 강력히 권장합니다.
NSUserDefaults는 주기적인 디스크 동기화와 함께 메모리 캐싱 원리로 작동합니다. 표준 UserDefaults.standard 인스턴스에 처음 접근하면 시스템이 plist 파일을 Dictionary로 RAM에 로드합니다. 모든 후속 읽기는 메모리에서 수행됩니다. 쓰기도 먼저 메모리에서 발생하며, 디스크 동기화는 백그라운드 스레드에서 주기적으로 발생합니다.
쓰기 작업은 set(_:forKey:) 메서드를 사용하며, 선택적 Any? 유형의 값을 허용합니다. 값은 nil이 될 수 있으며, 이는 키를 제거하는 데 사용됩니다. 즉각적인 디스크 쓰기를 위해 이전에는 synchronize() 메서드가 사용되었지만, iOS 7 및 OS X 10.9부터는 더 이상 필요하지 않습니다. 시스템이 정기적으로 자동으로 데이터를 동기화합니다. Apple은 공식 문서에서 synchronize()를 중복된 것으로 선언했습니다.
NSUserDefaults는 값 검색을 구성하기 위해 레지스터(도메인) 시스템을 사용합니다. 애플리케이션이 키로 값을 요청하면 UserDefaults는 특정 순서로 도메인을 순차적으로 확인합니다: 먼저 NSArgumentDomain(명령줄 인수), 그 다음 애플리케이션 도메인, 그 다음 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:) 쓰기 메서드가 있습니다. UserDefaults의 Swift 버전은 강력한 유형 지정을 사용하지만 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 |
NSUserDefaults의 synchronize() 메서드는 모든 변경 사항을 메모리에서 디스크로 강제로 기록합니다. 초기 iOS 버전에서는 데이터 지속성을 보장하기 위해 모든 쓰기 후에 이 메서드를 호출해야 했습니다. iOS 7부터 시스템이 백그라운드 스레드에서 UserDefaults를 자동으로 동기화하며, Apple은 공식적으로 synchronize()를 중복된 것으로 선언했습니다. 이 메서드를 호출해도 오류가 발생하지 않지만 추가적인 지속성 보장을 제공하지는 않습니다.
변경 사항을 모니터링하기 위해 NSUserDefaults는 UserDefaults.didChangeNotification 알림과 KVO 관찰 메서드 addObserver(_:forKeyPath:options:context:)를 제공합니다. SwiftUI에서는 @AppStorage Property Wrapper를 사용할 수 있으며, UserDefaults의 값을 UI 업데이트와 자동으로 동기화합니다. @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(애플리케이션과 그 확장 간의 공유 저장소) 작업을 위해 App Group 식별자와 함께 UserDefaults(suiteName:) 초기화 프로그램을 사용합니다. 예: “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의 복잡성입니다. 단순히 문자열을 저장하려면 클래스(kSecClassGenericPassword), 서비스(kSecAttrService), 계정(kSecAttrAccount) 및 실제 데이터(kSecValueData) 속성을 지정하여 SecItemAdd 쿼리를 생성해야 합니다. Keychain 작업을 단순화하기 위해 KeychainAccess 및 SwiftKeychainWrapper와 같은 타사 래퍼가 있으며, UserDefaults와 유사한 편리한 키-값 인터페이스를 제공합니다.
실용적인 예를 살펴보겠습니다: NSUserDefaults를 사용하여 iOS 애플리케이션에서 온보딩 상태(환영 화면)를 저장하고 복원합니다. 첫 실행 시 사용자는 온보딩 화면을 보고, 완료 후 UserDefaults에 플래그가 저장됩니다. 이후 실행 시 온보딩은 건너뜁니다. SwiftUI의 경우 @AppStorage가 사용되고, UIKit의 경우 UserDefaults.standard에 직접 액세스합니다.
온보딩 상태 저장을 위해 UserDefaults 작업을 캡슐화하는 OnboardingManager를 만들어 보겠습니다. 관리자는 상태 확인을 위한 isOnboardingCompleted 속성과 플래그 설정을 위한 markOnboardingCompleted 메서드를 제공합니다. 오타를 방지하기 위해 저장소 키는 상수로 추출됩니다. 단위 테스트를 위해 관리자는 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를 사용하는 것이 좋습니다. 객체는 JSONEncoder를 통해 Data로 직렬화되고, set(_:forKey:)를 통해 저장되며, 읽을 때 JSONDecoder를 통해 Data에서 객체로 역직렬화됩니다. 이 접근 방식을 사용하면 유형 안전성을 잃지 않고 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은 저장된 데이터를 수십 킬로바이트로 제한할 것을 권장합니다. 큰 객체(이미지, 문서, 직렬화된 모델)를 저장하려면 Documents 디렉토리와 함께 FileManager 또는 CoreData를 사용하세요. 또한 UserDefaults는 데이터 스키마 버전 관리를 지원하지 않습니다. Codable 모델 구조가 변경되면 이전 데이터의 역직렬화가 실패할 수 있으며, 이는 애플리케이션 코드에서 처리해야 합니다.
자주 묻는 질문
둘 다 키-값 저장소이지만 NSUserDefaults는 더 많은 유형(Data, Date, Array, Dictionary)을 지원하고 iCloud와 자동으로 동기화됩니다. SharedPreferences는 XML에 데이터를 저장하고 NSUserDefaults는 plist 형식을 사용합니다. NSUserDefaults에는 계단식 검색이 있는 도메인 시스템이 있는 반면, SharedPreferences는 파일 이름이 있는 단순한 평면 구조를 사용합니다.
아니요, NSUserDefaults는 암호화 없이 일반 텍스트로 데이터를 저장합니다. 비밀번호, 토큰 및 암호화 키에는 Secure Enclave 수준에서 데이터를 암호화하는 Keychain을 사용하세요. Keychain은 비밀을 읽기 전에 생체 인증(Face ID / Touch ID)과 같은 액세스 속성도 지원합니다.
동일한 사용자의 기기 간 동기화를 위해 NSUbiquitousKeyValueStore — iCloud 클라우드 키-값 저장소를 사용하세요. 한 기기에서 이 서비스에 기록된 데이터는 동일한 iCloud 계정을 가진 다른 모든 기기에 자동으로 나타납니다. 최대 용량: 애플리케이션당 1MB, 1024개 키.
모든 데이터를 삭제하려면 애플리케이션의 Bundle Identifier와 함께 removePersistentDomain(forName:) 메서드를 호출하세요. 개별 값을 제거하려면 removeObject(forKey:)를 사용하세요. 완전한 설정 재설정의 경우: UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!). 모든 삭제는 즉시 인메모리 캐시에 적용됩니다.
Apple은 NSUserDefaults의 크기에 대해 엄격한 제한을 두지 않지만, 모든 저장된 데이터의 총 볼륨이 100KB를 초과하지 않는 것이 좋습니다. 더 큰 볼륨의 경우 CoreData 또는 FileManager를 사용하세요. 1MB 이상의 데이터를 저장하면 애플리케이션 시작 시 읽기 성능이 눈에 띄게 저하될 수 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.