NSUserDefaults là một kho lưu trữ dữ liệu key-value trên iOS, watchOS, tvOS và macOS, được thiết kế để lưu cài đặt và cấu hình ứng dụng. Dữ liệu được lưu trong tệp plist trong sandbox của ứng dụng và tự động đồng bộ với iCloud qua NSUbiquitousKeyValueStore. Theo tài liệu chính thức từ Apple Developer, 2025, NSUserDefaults hỗ trợ lưu trữ các kiểu nguyên thủy: String, Int, Bool, Float, Double, Data, Date, Array và Dictionary. Lớp này đã được đổi tên thành UserDefaults từ Swift 3, nhưng tên Objective-C NSUserDefaults vẫn được sử dụng rộng rãi trong cơ sở mã và tài liệu Apple.
Điểm chính
NSUserDefaults (UserDefaults trong Swift) là cơ chế tích hợp của Apple để lưu trữ các cặp key-value ở định dạng plist. Nó có sẵn trên tất cả các nền tảng Apple: iOS, iPadOS, watchOS, tvOS và macOS. Mục đích chính là lưu tùy chọn người dùng, trạng thái giao diện, cờ lần đầu khởi chạy, các tùy chọn đã chọn và dữ liệu đơn giản khác tồn tại qua các lần khởi động lại ứng dụng.
Mỗi ứng dụng iOS có một sandbox cô lập và NSUserDefaults được lưu trong thư mục Library/Preferences bên trong sandbox này trong một tệp có tên Bundle Identifier. Tệp plist chứa các cặp key-value trong đó key là một chuỗi và value là một trong các kiểu được hỗ trợ. Kích thước tệp không bị giới hạn, nhưng Apple khuyên chỉ lưu cài đặt trong UserDefaults, không phải lượng lớn dữ liệu.
Bắt đầu từ iOS 8, NSUserDefaults đã bắt đầu hỗ trợ App Groups — kho lưu trữ dùng chung giữa các ứng dụng của cùng một nhà phát triển và các tiện ích mở rộng của chúng (widget, ứng dụng đồng hành watchOS). Để làm điều này, sử dụng bộ khởi tạo init?(suiteName:) với một định danh App Group. Điều này cho phép, ví dụ, widget Today đọc cài đặt từ ứng dụng chính mà không cần nhân đôi logic lưu.
Về mặt vật lý, NSUserDefaults được lưu trong một tệp plist nhị phân tại: {Sandbox}/Library/Preferences/com.example.myapp.plist. Tệp sử dụng định dạng plist nhị phân (NSPropertyListBinaryFormat_v1_0) để nhỏ gọn và tốc độ đọc. Trên macOS, tệp có thể ở định dạng XML để tương thích. Khác với SharedPreferences trên Android, các tệp plist của UserDefaults có thể chứa cấu trúc lồng nhau thông qua Dictionary và Array.
Các tệp NSUserDefaults không được mã hóa theo mặc định. Dữ liệu được lưu dưới dạng văn bản thuần túy và có thể được đọc khi có quyền truy cập vật lý vào thiết bị hoặc qua bản sao lưu. Để lưu dữ liệu nhạy cảm (mật khẩu, token, khóa mã hóa), Apple khuyên cáo sử dụng Keychain, tự động mã hóa dữ liệu ở cấp hệ điều hành.
NSUserDefaults hoạt động theo nguyên tắc lưu cache trong bộ nhớ với đồng bộ hóa đĩa định kỳ. Khi lần đầu truy cập vào thể hiện chuẩn UserDefaults.standard, hệ thống tải tệp plist vào RAM dưới dạng Dictionary. Tất cả các lần đọc tiếp theo được thực hiện từ bộ nhớ. Việc ghi cũng diễn ra trong bộ nhớ trước, với đồng bộ hóa đĩa diễn ra định kỳ trên một luồng nền.
Các thao tác ghi sử dụng phương thức set(_:forKey:), chấp nhận một giá trị tùy chọn thuộc kiểu Any?. Giá trị có thể là nil — dùng để xóa một key. Để ghi ngay lập tức vào đĩa, trước đây đã sử dụng phương thức synchronize(), nhưng từ iOS 7 và OS X 10.9, nó không còn cần thiết nữa — hệ thống tự động đồng bộ dữ liệu theo các khoảng thời gian định kỳ. Apple đã chính thức tuyên bố synchronize() là dư thừa trong tài liệu của mình.
NSUserDefaults sử dụng hệ thống thanh ghi (miền) để tổ chức tra cứu giá trị. Khi một ứng dụng yêu cầu một giá trị theo key, UserDefaults kiểm tra tuần tự các miền theo một thứ tự cụ thể: đầu tiên NSArgumentDomain (các đối số dòng lệnh), sau đó miền Ứng dụng, sau đó NSGlobalDomain (cài đặt hệ thống), sau đó các miền cụ thể theo ngôn ngữ và cuối cùng là NSRegistrationDomain (các giá trị mặc định được đăng ký qua register(defaults:)).
import Foundation
// Thể hiện chuẩn của UserDefaults
let defaults = UserDefaults.standard
// Ghi giá trị
defaults.set("Anna Petrova", forKey: "username")
defaults.set(28, forKey: "age")
defaults.set(true, forKey: "isLoggedIn")
// Đăng ký giá trị mặc định
defaults.register(defaults: [
"theme": "system",
"fontSize": 14
])
// Đọc với trả về giá trị mặc định
let theme = defaults.string(forKey: "theme") ?? "system"
let fontSize = defaults.integer(forKey: "fontSize")
Miền NSRegistrationDomain là một miền lập trình chỉ tồn tại trong bộ nhớ và không được lưu vào đĩa. Nó được sử dụng để đặt các giá trị mặc định áp dụng cho đến khi ứng dụng ghi giá trị riêng của nó vào miền Ứng dụng. Điều này cho phép tạo một điểm cấu hình duy nhất cho các cài đặt mặc định có thể được thay đổi tập trung trong quá trình phát triển.
NSUserDefaults cung cấp một tập hợp các phương thức được phân loại để đọc và ghi dữ liệu: string(forKey:), integer(forKey:), bool(forKey:), float(forKey:), double(forKey:), data(forKey:), array(forKey:), dictionary(forKey:) và object(forKey:). Mỗi phương thức đọc có một phương thức ghi tương ứng set(_:forKey:) với tự động xác định kiểu của giá trị được lưu. Phiên bản Swift của UserDefaults sử dụng kiểu mạnh, nhưng phiên bản Objective-C chấp nhận và trả về id.
| Phương thức đọc (Swift) | Kiểu dữ liệu | Giá trị mặc định |
|---|---|---|
| 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 |
Phương thức synchronize() trong NSUserDefaults buộc ghi tất cả các thay đổi từ bộ nhớ vào đĩa. Trong các phiên bản đầu của iOS, phương thức này phải được gọi sau mỗi lần ghi để đảm bảo dữ liệu được lưu. Bắt đầu từ iOS 7, hệ thống tự động đồng bộ UserDefaults trên một luồng nền và Apple đã chính thức tuyên bố synchronize() là dư thừa. Gọi phương thức này không gây ra lỗi nhưng không cung cấp thêm bảo đảm về tính lâu dài.
Để theo dõi các thay đổi, NSUserDefaults cung cấp thông báo UserDefaults.didChangeNotification và phương thức quan sát KVO addObserver(_:forKeyPath:options:context:). Trong SwiftUI, Property Wrapper @AppStorage có sẵn, tự động đồng bộ một giá trị trong UserDefaults với cập nhật giao diện người dùng. @AppStorage hỗ trợ các kiểu giống như UserDefaults và là cách ưa tiên để làm việc với cài đặt trong các ứng dụng SwiftUI.
// Quan sát thay đổi qua KVO
class SettingsViewModel: NSObject {
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?
) {
guard let keyPath else { return }
print("Khóa đã thay đổi: \(keyPath)")
}
}
// SwiftUI - AppStorage
struct SettingsView: View {
@AppStorage("theme") private var theme: String = "system"
var body: some View {
Picker("Chủ đề", selection: $theme) {
Text("Hệ thống").tag("system")
Text("Sáng").tag("light")
Text("Tối").tag("dark")
}
}
}
Để làm việc với App Groups (kho lưu trữ dùng chung giữa ứng dụng và các tiện ích mở rộng của nó), sử dụng bộ khởi tạo UserDefaults(suiteName:) với một định danh App Group. Ví dụ, “group.com.example.myapp”. Dữ liệu được ghi vào thể hiện này có thể truy cập từ ứng dụng chính, widget, ứng dụng đồng hành watchOS và các tiện ích mở rộng khác thuộc cùng một App Group. Mỗi thể hiện suite được lưu trong một tệp plist riêng.
Mặc dù tiện lợi và đơn giản, NSUserDefaults không phải là giải pháp lưu trữ phổ quát cho tất cả các kiểu dữ liệu trên iOS. Tùy thuộc vào khối lượng, tính chất quan trọng và yêu cầu bảo mật, Apple cung cấp một số lựa chọn thay thế, mỗi lựa chọn được tối ưu hóa cho một trường hợp sử dụng cụ thể.
| Giải pháp | Khi nào sử dụng | Hạn chế |
|---|---|---|
| NSUserDefaults | Cài đặt giao diện và cấu hình | Không phù hợp cho dữ liệu lớn hoặc bí mật |
| Keychain | Mật khẩu, token, khóa mã hóa | Phức tạp hơn khi sử dụng, chậm hơn |
| CoreData | Dữ liệu có cấu trúc với quan hệ | Quá mức cho 10–20 cài đặt |
| FileManager | Tài liệu, hình ảnh, dữ liệu nhị phân | Yêu cầu quản lý tệp thủ công |
| CloudKit | Đồng bộ đám mây giữa các thiết bị | Yêu cầu tài khoản iCloud và kết nối mạng |
Keychain là kho lưu trữ an toàn của Apple cho dữ liệu bảo mật. Khác với NSUserDefaults, tất cả dữ liệu trong Keychain đều được mã hóa ở cấp hệ điều hành bằng mã hóa phần cứng Secure Enclave trên các thiết bị tương thích. Keychain tự động khóa và mở khóa cùng với thiết bị và hỗ trợ chia sẻ quyền truy cập giữa các ứng dụng của cùng một nhà phát triển thông qua Keychain Access Groups.
Nhược điểm chính của Keychain là sự phức tạp của API. Để lưu một chuỗi đơn giản, bạn cần tạo một truy vấn SecItemAdd với các thuộc tính: lớp (kSecClassGenericPassword), dịch vụ (kSecAttrService), tài khoản (kSecAttrAccount) và dữ liệu thực tế (kSecValueData). Để đơn giản hóa việc làm việc với Keychain, có các wrapper bên thứ ba như KeychainAccess và SwiftKeychainWrapper cung cấp giao diện key-value thuận tiện tương tự UserDefaults.
Hãy xem xét một ví dụ thực tế: lưu và khôi phục trạng thái onboarding (màn hình chào mừng) trong một ứng dụng iOS sử dụng NSUserDefaults. Khi khởi chạy lần đầu, người dùng thấy màn hình onboarding; sau khi hoàn thành, một cờ được lưu trong UserDefaults. Ở các lần khởi chạy sau, onboarding được bỏ qua. Đối với SwiftUI, @AppStorage được sử dụng; đối với UIKit, truy cập trực tiếp vào UserDefaults.standard.
Hãy tạo một OnboardingManager đóng gói việc làm việc với UserDefaults để lưu trạng thái onboarding. Trình quản lý cung cấp thuộc tính isOnboardingCompleted để kiểm tra trạng thái và phương thức markOnboardingCompleted để đặt cờ. Key lưu trữ được trích xuất thành hằng số để ngăn lỗi chính tả. Đối với kiểm thử đơn vị, trình quản lý sử dụng giao thức UserDefaultsProtocol, cho phép thay thế kho lưu trữ thực bằng 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)
}
}
// Sử dụng trong ứng dụng
let onboardingManager = OnboardingManager()
if !onboardingManager.isOnboardingCompleted {
showOnboarding()
} else {
showMainScreen()
}
Để lưu các cài đặt phức tạp hơn, chẳng hạn như một đối tượng Profile có cấu trúc, nên sử dụng giao thức Codable và JSONEncoder/JSONDecoder. Đối tượng được tuần tự hóa thành Data qua JSONEncoder, được lưu qua set(_:forKey:), và khi đọc được giải tuần tự hóa từ Data trở lại đối tượng qua JSONDecoder. Cách tiếp cận này cho phép lưu các cấu trúc phức tạp trong UserDefaults mà không mất an toàn kiểu.
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)
}
}
// Sử dụng
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")
Điều quan trọng cần nhớ là NSUserDefaults không được thiết kế để lưu trữ khối lượng lớn dữ liệu. Apple khuyên giới hạn dữ liệu được lưu ở mức vài chục kilobyte. Để lưu các đối tượng lớn (hình ảnh, tài liệu, mô hình đã tuần tự hóa), hãy sử dụng FileManager với thư mục Documents hoặc CoreData. Ngoài ra, UserDefaults không hỗ trợ quản lý phiên bản lược đồ dữ liệu — khi cấu trúc mô hình Codable thay đổi, dữ liệu cũ có thể không giải tuần tự được và điều này cần được xử lý trong mã ứng dụng.
Câu hỏi thường gặp
Cả hai đều là kho lưu trữ key-value, nhưng NSUserDefaults hỗ trợ nhiều kiểu hơn (Data, Date, Array, Dictionary) và tự động đồng bộ với iCloud. SharedPreferences lưu dữ liệu trong XML, NSUserDefaults ở định dạng plist. NSUserDefaults có hệ thống miền với tra cứu xếp tầng, trong khi SharedPreferences sử dụng cấu trúc phẳng đơn giản với tên tệp.
Không, NSUserDefaults lưu dữ liệu dưới dạng văn bản thuần túy không mã hóa. Đối với mật khẩu, token và khóa mã hóa, hãy sử dụng Keychain, mã hóa dữ liệu ở cấp Secure Enclave. Keychain cũng hỗ trợ các thuộc tính truy cập như xác thực sinh trắc học (Face ID / Touch ID) trước khi đọc bí mật.
Để đồng bộ giữa các thiết bị của cùng một người dùng, hãy sử dụng NSUbiquitousKeyValueStore — kho lưu trữ key-value đám mây iCloud. Dữ liệu được ghi vào dịch vụ này trên một thiết bị sẽ tự động xuất hiện trên tất cả các thiết bị khác có cùng tài khoản iCloud. Dung lượng tối đa: 1 MB cho mỗi ứng dụng, 1024 key.
Để xóa tất cả dữ liệu, hãy gọi phương thức removePersistentDomain(forName:) với Bundle Identifier của ứng dụng. Để xóa từng giá trị riêng lẻ, sử dụng removeObject(forKey:). Để đặt lại hoàn toàn cài đặt: UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!). Tất cả các xóa được áp dụng ngay lập tức vào bộ nhớ cache.
Apple không đặt giới hạn cứng nhắc cho kích thước NSUserDefaults, nhưng nên không vượt quá 100 KB tổng khối lượng tất cả dữ liệu được lưu. Đối với khối lượng lớn hơn, hãy sử dụng CoreData hoặc FileManager. Khi lưu hơn 1 MB dữ liệu, hiệu suất đọc khi khởi động ứng dụng có thể giảm đáng kể.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm