@AppStorage trong SwiftUI là property wrapper để làm việc với UserDefaults, tự động đồng bộ giá trị với UI. Khi một thuộc tính được khai báo qua @AppStorage thay đổi, giá trị mới ngay lập tức được lưu vào UserDefaults, và khi UserDefaults thay đổi từ bên ngoài — bởi widget hoặc extension — View tự động vẽ lại. Theo Apple Developer Documentation (2025), @AppStorage hỗ trợ String, Int, Double, Bool, Data, URL và các phiên bản tùy chọn của chúng, cung cấp khả năng lưu trữ phản ứng cho cài đặt người dùng mà không cần mã quan sát thủ công.
Những điểm chính
@AppStorage là property wrapper do Apple giới thiệu trong iOS 14, liên kết một thuộc tính View với một khóa trong UserDefaults. Khi đọc thuộc tính, SwiftUI tải giá trị từ UserDefaults qua khóa đã chỉ định. Khi ghi, nó lưu giá trị mới và thông báo cho View cần vẽ lại.
Trước @AppStorage, các nhà phát triển phải đọc UserDefaults thủ công trong onAppear, đăng ký UserDefaults.didChangeNotification và cập nhật @State khi thay đổi. @AppStorage tự động hóa toàn bộ chu trình: một dòng khai báo thay thế 15–20 dòng mã boilerplate. Hơn nữa, @AppStorage cung cấp đồng bộ hai chiều — nếu giá trị UserDefaults thay đổi từ một tiến trình khác (ví dụ: App Extension hoặc Widget), View vẫn nhận được cập nhật.
Về mặt kiến trúc, @AppStorage được triển khai dưới dạng DynamicProperty, cho phép SwiftUI theo dõi các phụ thuộc và vẽ lại View khi giá trị được quan sát thay đổi. Điều này làm cho nó trở nên lý tưởng để lưu trữ cài đặt người dùng: ngôn ngữ giao diện, bật/tắt tính năng, tab cuối cùng được chọn, tên người dùng.
Mặc dù @AppStorage sử dụng UserDefaults bên trong, nhưng cách tiếp cận làm việc với bộ nhớ về cơ bản là khác nhau. UserDefaults là API cấp thấp yêu cầu quản lý thủ công việc đọc, ghi và thông báo thay đổi. @AppStorage là một abstraction của SwiftUI cung cấp hành vi phản ứng ngay lập tức.
UserDefaults phù hợp cho các thao tác một lần: tải cài đặt khi khởi động ứng dụng, ghi phân tích, lưu token vào bộ nhớ đệm. @AppStorage dành cho các cài đặt cần cập nhật UI một cách phản ứng: công tắc chủ đề, chọn ngôn ngữ, lưu trạng thái giao diện. Sử dụng UserDefaults trực tiếp bên trong View là một phản mẫu, vì View không biết về các thay đổi nếu không có đăng ký bổ sung.
| Tham số | @AppStorage | UserDefaults |
|---|---|---|
| Tính phản ứng | Tự động | Cần đăng ký thông báo |
| Boilerplate | 1 dòng mỗi thuộc tính | 15–20 dòng mỗi thuộc tính |
| Kiểu dữ liệu | String, Int, Double, Bool, Data, URL | Tất cả các kiểu + đối tượng lưu trữ |
| Kiểu tùy chỉnh | Qua RawRepresentable | Qua NSKeyedArchiver |
| App Extension | Đồng bộ tự động | Đăng ký thủ công |
Đối với cài đặt đơn giản với UI phản ứng, @AppStorage là lựa chọn ưu tiên. Đối với dữ liệu phức tạp (mảng, từ điển, đối tượng tùy chỉnh), hãy sử dụng kết hợp UserDefaults với @State và đăng ký thay đổi thủ công, hoặc chuyển sang SwiftData / Core Data để lưu trữ có cấu trúc.
@AppStorage hỗ trợ các kiểu tiêu chuẩn mà UserDefaults có thể tuần tự hóa trực tiếp: String, Int, Double, Bool, Data, URL. Mỗi kiểu đều có phiên bản tùy chọn (String?, Int?, Double?, Bool?, Data?, URL?), cho phép phân biệt giữa "chưa đặt" và "giá trị rỗng".
Để lưu trữ các kiểu tùy chỉnh tuân thủ giao thức RawRepresentable, @AppStorage cũng hoạt động tự động. Nếu enum có rawValue kiểu String hoặc Int, nó có thể được sử dụng trực tiếp: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI tự động tuần tự hóa/giải tuần tự hóa giá trị qua rawValue.
enum AppTheme: String {
case system, light, dark
}
struct SettingsView: View {
@AppStorage("username") var username: String = "Guest"
@AppStorage("launchCount") var launchCount: Int = 0
@AppStorage("isDarkMode") var isDarkMode: Bool = false
@AppStorage("appTheme") var theme: AppTheme = .system
@AppStorage("lastOpened") var lastOpened: Date? = nil
var body: some View {
Form {
TextField("Username", text: $username)
Toggle("Dark mode", isOn: $isDarkMode)
Text("Đã khởi chạy \(launchCount) lần")
}
}
}
Ví dụ sử dụng các kiểu @AppStorage khác nhau: String với giá trị mặc định "Guest", Int cho bộ đếm số lần khởi chạy, Bool cho chủ đề tối, enum AppTheme với rawValue kiểu String và Date? tùy chọn cho thời gian mở lần cuối. Mỗi thuộc tính được liên kết với một khóa UserDefaults được chỉ định làm đối số đầu tiên. Giá trị mặc định được sử dụng nếu khóa không có trong bộ nhớ khi khởi chạy lần đầu.
Một trong những lợi thế chính của @AppStorage là tự động quan sát các thay đổi UserDefaults từ bất kỳ nguồn nào. Nếu App Extension hoặc Widget thay đổi giá trị, @AppStorage trong ứng dụng cha nhận được thông báo và vẽ lại View. Điều này đạt được thông qua cơ chế KVO (Key-Value Observing) mà @AppStorage tự động thiết lập trên UserDefaults.didChangeNotification.
Trong thực tế, điều này có nghĩa là nếu người dùng thay đổi cài đặt trong Widget (ví dụ: bật chủ đề tối), ứng dụng sẽ ngay lập tức nắm bắt thay đổi đó. Đồng bộ tương tự hoạt động giữa ứng dụng chính và Share Extension, Watch App hoặc Today Widget. Nhà phát triển không cần viết mã cho trao đổi dữ liệu giữa các tiến trình — @AppStorage thực hiện điều đó tự động.
struct ThemeSettingView: View {
@AppStorage("isDarkMode") var isDarkMode: Bool = false
var body: some View {
VStack {
Toggle("Dark Mode", isOn: $isDarkMode)
.onChange(of: isDarkMode) { oldValue, newValue in
print("Chế độ tối đã thay đổi thành \(newValue)")
}
}
}
}
Toggle được liên kết với $isDarkMode qua @AppStorage. Khi chuyển đổi, giá trị tự động được lưu vào UserDefaults dưới khóa "isDarkMode". Bộ sửa đổi .onChange cho phép thực hiện hiệu ứng phụ khi thay đổi — ví dụ: gửi phân tích hoặc cập nhật UI của các màn hình khác. Nếu Widget thay đổi cùng một khóa, @AppStorage cũng sẽ kích hoạt onChange, đảm bảo tính nhất quán của trạng thái.
Hãy xem xét một màn hình cài đặt ứng dụng hoàn chỉnh sử dụng @AppStorage để lưu trữ tất cả cấu hình. Biểu mẫu chứa các phần với nhiều loại cài đặt khác nhau: trường văn bản, công tắc, bộ đếm — tất cả giá trị đều tự động được lưu vào UserDefaults.
struct AppSettingsView: View {
@AppStorage("displayName") var displayName = ""
@AppStorage("notificationsEnabled") var notificationsEnabled = true
@AppStorage("maxResults") var maxResults = 25
@AppStorage("selectedTab") var selectedTab = "home"
var body: some View {
NavigationStack {
Form {
Section(header: Text("Hồ sơ")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("Tùy chọn")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("Đặt lại cài đặt") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
Biểu mẫu chứa bốn thuộc tính @AppStorage thuộc các kiểu khác nhau: String cho tên, Bool cho thông báo, Int cho số lượng kết quả và String cho tab đã chọn. Tất cả các điều khiển được liên kết với các thuộc tính qua Binding ($displayName, $notificationsEnabled, v.v.). Nút "Reset settings" xóa tất cả UserDefaults bằng cách xóa miền ứng dụng — sau đó @AppStorage tự động trở về giá trị mặc định.
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "hệ thống"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "xanh dương"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("Hệ thống").tag("system")
Text("Sáng").tag("sáng")
Text("Tối").tag("tối")
}
Picker("Accent color", selection: $widgetColor) {
Text("Xanh dương").tag("blue")
Text("Xanh lá").tag("xanh lá")
Text("Đỏ").tag("đỏ")
}
}
}
}
Đối với App Group (bộ nhớ dùng chung giữa ứng dụng và tiện ích mở rộng), @AppStorage chấp nhận tham số store: UserDefaults(suiteName:). Các giá trị được lưu trong vùng chứa dùng chung có sẵn cho ứng dụng chính, Widget, Watch App và các tiện ích mở rộng khác trong cùng nhóm. Widget có thể đọc các cài đặt này và khi chúng thay đổi trong ứng dụng, Widget sẽ tự động cập nhật thông qua cơ chế quan sát UserDefaults.
Câu hỏi thường gặp
@State lưu trữ giá trị chỉ trong bộ nhớ và được đặt lại khi khởi động lại ứng dụng. @AppStorage lưu giá trị trong UserDefaults và khôi phục nó ở lần khởi chạy tiếp theo. Sử dụng @State cho dữ liệu màn hình tạm thời, @AppStorage cho cài đặt cần tồn tại sau khi khởi động lại.
Có, nếu Enum triển khai giao thức RawRepresentable với rawValue kiểu String hoặc Int. Ví dụ: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI tự động tuần tự hóa enum qua rawValue và khôi phục khi tải.
Gọi UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) cho bộ nhớ tiêu chuẩn hoặc removeObject(forKey:) cho một khóa cụ thể. Sau khi xóa, tất cả các thuộc tính @AppStorage sẽ trở về giá trị mặc định được chỉ định trong khai báo.
Có, để đồng bộ giữa ứng dụng và tiện ích mở rộng, hãy sử dụng App Group: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!). Widget, Share Extension và Watch App có thể đọc và ghi vào cùng một UserDefaults, và các thay đổi được theo dõi tự động.
@AppStorage sử dụng UserDefaults, được thiết kế cho lượng dữ liệu nhỏ: cài đặt, token, bộ đếm. Giới hạn khuyến nghị lên tới 100 KB cho mỗi ứng dụng. Đối với dữ liệu có cấu trúc hoặc lớn (mảng đối tượng, tệp phương tiện), hãy sử dụng SwiftData, Core Data hoặc hệ thống tệp.
Tổng kế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