@AppStorage trong SwiftUI — nó là gì, UserDefaults và lưu trữ cài đặt

Tác giả: IT Sectr Đã đăng: 2026-06-25 Thời gian đọc: 7 phút

@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 — property wrapper để làm việc phản ứng với UserDefaults trong SwiftUI
  • Tự động lưu — giá trị được ghi vào UserDefaults mỗi khi thay đổi
  • Tự động cập nhật UI — View vẽ lại khi UserDefaults thay đổi từ bất kỳ nguồn nào
  • Các kiểu được hỗ trợ: String, Int, Double, Bool, Data, URL và phiên bản tùy chọn
  • Giá trị mặc định được đặt trong khai báo và sử dụng khi khởi chạy lần đầu

@AppStorage trong SwiftUI là gì?

@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.

@AppStorage vs UserDefaults: So sánh

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ố@AppStorageUserDefaults
Tính phản ứngTự độngCần đăng ký thông báo
Boilerplate1 dòng mỗi thuộc tính15–20 dòng mỗi thuộc tính
Kiểu dữ liệuString, Int, Double, Bool, Data, URLTất cả các kiểu + đối tượng lưu trữ
Kiểu tùy chỉnhQua RawRepresentableQua 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.

Các kiểu dữ liệu được hỗ trợ

@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.

swift
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.

Quan sát thay đổi Store

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.

swift
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.

Ví dụ mã @AppStorage

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.

swift
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.

Đồng bộ @AppStorage với App Group

swift
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

Sự khác biệt giữa @AppStorage và @State là gì?

@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ó thể sử dụng @AppStorage với Enum không?

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.

Làm thế nào để xóa tất cả giá trị @AppStorage?

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.

@AppStorage có hoạt động với App Extensions không?

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.

Có thể lưu trữ bao nhiêu dữ liệu trong @AppStorage?

@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

  • @AppStorage — property wrapper để lưu trữ cài đặt phản ứng trong UserDefaults
  • Tự động lưu và tự động cập nhật UI khi giá trị thay đổi từ bất kỳ nguồn nào
  • Hỗ trợ String, Int, Double, Bool, Data, URL và enum RawRepresentable
  • Giá trị mặc định được đặt trong khai báo và khôi phục khi khởi chạy lần đầu
  • App Group cho phép đồng bộ @AppStorage giữa ứng dụng và tiện ích mở rộng
  • UserDefaults chỉ phù hợp cho lượng dữ liệu nhỏ — lên tới 100 KB
  • Sử dụng @AppStorage cho cài đặt người dùng, @State cho trạng thái màn hình tạm thời

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.

Thảo luận dự án

Đọc thêm