SwiftUI 中的 @AppStorage — 它是什么、UserDefaults 和存储设置

作者: IT Sectr 发布日期: 2026-06-25 阅读时间: 7 分钟

SwiftUI 中的 @AppStorage — 是用于 UserDefaults 的 property wrapper,能自动将值与 UI 同步。当通过 @AppStorage 声明的属性发生变更时,新值立即保存到 UserDefaults 中,当 UserDefaults 从外部(例如小组件或扩展)发生变更时,View 自动重绘。据 Apple Developer Documentation (2025)@AppStorage 支持 String、Int、Double、Bool、Data、URL 及其可选版本,无需手动观察代码即可实现用户设置的响应式存储。

主要内容

  • @AppStorage — 用于在 SwiftUI 中与 UserDefaults 进行响应式工作的 property wrapper
  • 自动保存 — 每次变更时值被写入 UserDefaults
  • 自动更新 UI — 当 UserDefaults 从任何源发生变更时,View 自动重绘
  • 支持的类型:String、Int、Double、Bool、Data、URL 及可选版本
  • 默认值 在声明中设置,并在首次启动时使用

SwiftUI 中的 @AppStorage 是什么?

@AppStorage 是苹果在 iOS 14 中引入的 property wrapper,将 View 的属性与 UserDefaults 中的键关联。读取属性时,SwiftUI 根据指定的键从 UserDefaults 加载值。写入时 — 保存新值并通知 View 需要重绘。

在 @AppStorage 出现之前,开发人员必须在 onAppear 中手动读取 UserDefaults,订阅 UserDefaults.didChangeNotification 通知,并在变更时更新 @State。@AppStorage 自动化了整个循环:一行声明取代了 15–20 行样板代码。此外,@AppStorage 提供双向同步 — 如果 UserDefaults 值从另一进程(例如 App Extension 或 Widget)发生变更,View 仍会接收到更新。

从架构上看,@AppStorage 作为 DynamicProperty 实现,这使得 SwiftUI 能够跟踪依赖关系并在被观察的值变更时重绘 View。这使它成为存储用户设置的理想选择:界面语言、功能开启/关闭、最后选择的标签页、用户名。

@AppStorage 与 UserDefaults 对比

尽管 @AppStorage 在底层使用 UserDefaults,但存储方法根本不同。UserDefaults 是低级 API,需要手动管理读取、写入和变更通知。@AppStorage 是 SwiftUI 抽象,开箱即用提供响应式行为。

UserDefaults 适合一次性操作:应用启动时加载设置、写入分析数据、缓存 token。@AppStorage — 适合需要响应式更新 UI的设置:主题开关、语言选择、界面状态保存。在 View 内直接使用 UserDefaults 是反模式,因为没有额外订阅,View 无法知道变更。

参数@AppStorageUserDefaults
响应性自动需要订阅通知
样板代码每个属性 1 行每个属性 15–20 行
类型String、Int、Double、Bool、Data、URL所有类型 + 归档对象
自定义类型通过 RawRepresentable通过 NSKeyedArchiver
App Extension自动同步手动订阅

对于简单的响应式 UI 设置,@AppStorage 是更佳选择。对于复杂数据(数组、字典、自定义对象),请使用 UserDefaults 与 @State 和手动变更订阅的组合,或转向 SwiftData / Core Data 进行结构化存储。

支持的数据类型

@AppStorage 支持 UserDefaults 可以直接序列化的标准类型:String、Int、Double、Bool、Data、URL。每种类型都有可选版本(String?、Int?、Double?、Bool?、Data?、URL?),可以区分“未设置”和“空值”。

对于符合 RawRepresentable 协议的自定义类型,@AppStorage 也自动工作。如果枚举的 rawValue 类型为 String 或 Int,可以直接使用:@AppStorage("theme") var theme: AppTheme = .system。SwiftUI 通过 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("已启动 \(launchCount) 次")
        }
    }
}

示例中使用了不同类型的 @AppStorage:默认值为 “Guest” 的 String、用于启动次数计数的 Int、用于深色主题的 Bool、rawValue 类型为 String 的 AppTheme 枚举以及可选的 Date?。每个属性都绑定到作为第一个参数指定的 UserDefaults 键。如果首次启动时键不存在,则使用默认值。

观察 Store 变更

@AppStorage 的主要优势之一 — 自动观察来自任何源的 UserDefaults 变更。如果 App Extension 或 Widget 修改了值,父应用程序中的 @AppStorage 会接收通知并重绘 View。这通过 KVO(Key-Value Observing)机制实现,@AppStorage 自动将其设置在 UserDefaults.didChangeNotification 上。

在实践中,这意味着如果用户在小组件中改变设置(例如启用深色主题),应用程序立即捕获这一变更。主应用程序与 Share Extension、Watch App 或 Today Widget 之间的同步也类似工作。开发人员无需编写 进程间数据交换 代码 — @AppStorage 自动完成。

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("深色模式已更改为 \(newValue)")
                }
        }
    }
}

Toggle 通过 @AppStorage 与 $isDarkMode 连接。切换时,值自动保存到 UserDefaults 中的 “isDarkMode” 键下。.onChange 修饰符可在变更时执行副作用 — 例如发送分析数据或更新其他屏幕的 UI。如果小组件修改了相同的键,@AppStorage 也会触发 onChange,保证状态一致性。

@AppStorage 代码示例

让我们查看一个完整的应用设置屏幕,它使用 @AppStorage 存储所有配置。表单包含不同类型设置的部分:文本字段、开关、计数器 — 所有值自动保存到 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("个人资料")) {
                    TextField("Display name", text: $displayName)
                }

                Section(header: Text("偏好设置")) {
                    Toggle("Enable notifications",
                           isOn: $notificationsEnabled)
                    Stepper("Max results: \(maxResults)",
                           value: $maxResults,
                           in: 10...100,
                           step: 5)
                }

                Section {
                    Button("重置设置") {
                        UserDefaults.standard.removePersistentDomain(
                            forName: Bundle.main.bundleIdentifier!)
                    }
                    .tint(.red)
                }
            }
            .navigationTitle("Settings")
        }
    }
}

表单包含四个不同类型的 @AppStorage 属性:属性名称的 String、通知的 Bool、结果数量的 Int 和所选标签页的 String。所有控件通过 Binding 与属性连接($displayName、$notificationsEnabled 等)。“Reset settings” 按钮重置所有 UserDefaults,删除应用程序域 — 之后 @AppStorage 自动返回默认值。

与 App Group 同步 @AppStorage

swift
struct SharedSettingsView: View {
    let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")

    @AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
    var widgetTheme: String = "系统"

    @AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
    var widgetColor: String = "蓝色"

    var body: some View {
        Form {
            Picker("Widget theme", selection: $widgetTheme) {
                Text("系统").tag("system")
                Text("浅色").tag("浅色")
                Text("深色").tag("深色")
            }
            Picker("Accent color", selection: $widgetColor) {
                Text("蓝色").tag("blue")
                Text("绿色").tag("绿色")
                Text("红色").tag("红色")
            }
        }
    }
}

对于 App Group(应用程序与扩展之间的共享存储),@AppStorage 接受 store 参数:UserDefaults(suiteName:)。值存储在主应用程序、Widget、Watch App 及同一组的其他扩展均可访问的共享容器中。Widget 可以读取这些设置,并在应用程序变更时通过 UserDefaults 观察机制自动更新。

常见问题

@AppStorage 和 @State 之间有什么区别?

@State 仅在内存中存储值,应用重启时会重置。@AppStorage 将值保存在 UserDefaults 中,并在下次启动时恢复。请使用 @State 处理临时屏幕数据,@AppStorage 处理需要在重启后保留的设置。

可以将 @AppStorage 与枚举一起使用吗?

可以,只要枚举实现了 RawRepresentable 协议,rawValue 类型为 String 或 Int。示例:@AppStorage("theme") var theme: AppTheme = .system。SwiftUI 通过 rawValue 自动序列化枚举并在加载时恢复。

如何清除所有 @AppStorage 值?

对于标准存储,调用 UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!),或为特定键调用 removeObject(forKey:)。清除后,所有 @AppStorage 属性将返回声明中指定的默认值。

@AppStorage 能与 App Extensions 一起使用吗?

可以,为了在应用程序和扩展之间同步,请使用 App Group:@AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!)。Widget、Share Extension 和 Watch App 可以读写相同的 UserDefaults,变更自动被跟踪。

@AppStorage 可以存储多少数据?

@AppStorage 使用 UserDefaults,它专为小量数据设计:设置、token、计数器。建议限制为每个应用程序不超过 100 KB。对于结构化或大量数据(对象数组、多媒体文件),请使用 SwiftData、Core Data 或文件系统。

总结

  • @AppStorage — 用于在 UserDefaults 中响应式存储设置的 property wrapper
  • 自动保存和值从任何源变更时自动更新 UI
  • 支持 String、Int、Double、Bool、Data、URL 及 RawRepresentable 枚举
  • 默认值在声明中设置并在首次启动时恢复
  • App Group 可以在应用程序和扩展之间同步 @AppStorage
  • UserDefaults 仅适合小量数据 — 不超过 100 KB
  • 使用 @AppStorage 处理用户设置,@State 处理临时屏幕状态

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读