Data — AppStorage

什么是 @AppStorage #

SwiftUI 中,@AppStorage 是一个属性包装器,用于便捷地访问 UserDefaults 中存储的数据。它提供了一种现代化和类型安全的方式来读取和存储轻量级的键值对数据,尤其适用于 持久化跨应用会话的简单设定(如用户偏好、配置信息等)。

当数据发生变化时,@AppStorage 会自动通知视图刷新,从而实现数据绑定和 UI 更新。


基本用法 #

使用 @AppStorage 表示一个绑定到 UserDefaults 的属性,你只需要提供一个键名。

示例 1:静态示例 #

import SwiftUI

struct ContentView: View {
    @AppStorage("username") var username: String = "Guest" // 绑定 UserDefaults 中的 "username"

    var body: some View {
        VStack {
            Text("Hello, \(username)") // 绑定的值改变时,视图会自动刷新
            TextField("Enter your name", text: $username) // 修改绑定值
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
        }
        .padding()
    }
}

解释: #

  1. @AppStorage("username")
    • username 属性绑定到 UserDefaults 的键 "username",初始值为 Guest
  2. 自动监听
    • username 属性的值被用户在 TextField 中修改时,它会自动同步到 UserDefaults,并触发 Text 视图重新渲染。

@AppStorage 支持的数据类型 #

AppStorage 支持以下常见类型,与 UserDefaults 基本一致:

  • 基本数据类型Int, Double, Float, Bool, String
  • 集合类型(如 Data 默认支持,但需手动编码数据)。
  • 枚举(原始值必须是支持的类型,例如 StringInt)。

@AppStorage 使用案例 #

示例 2:存储布尔值(应用主题) #

import SwiftUI

struct ContentView: View {
    @AppStorage("isDarkMode") var isDarkMode: Bool = false // 是否启用暗模式

    var body: some View {
        VStack {
            Text("Current Theme: \(isDarkMode ? "Dark" : "Light")")
                .padding()
            
            Toggle("Enable Dark Mode", isOn: $isDarkMode)
                .padding()
        }
        .preferredColorScheme(isDarkMode ? .dark : .light) // 根据用户偏好设置界面主题
    }
}

解释:

  • isDarkMode 绑定到 UserDefaults 中的 "isDarkMode" 键。
  • 切换开关时,UserDefaults 的值会更新,并自动切换应用主题。

@AppStorage 设置自定义数据类型 #

虽然 @AppStorage 默认支持基础数据类型,但对于一些复杂数据(如自定义对象或枚举),你需要做额外的处理。例如,利用枚举的原始值(RawValue),或将自定义类型序列化为可以存储的格式(如 JSON)。


示例 3:存储枚举值(原始值) #

import SwiftUI

enum AppTheme: String { // 定义枚举
    case light, dark
}

struct ContentView: View {
    @AppStorage("selectedTheme") var selectedThemeRaw: String = AppTheme.light.rawValue // 存储枚举原始值

    var selectedTheme: AppTheme {
        get { AppTheme(rawValue: selectedThemeRaw) ?? .light }
        set { selectedThemeRaw = newValue.rawValue }
    }

    var body: some View {
        VStack {
            Text("Selected Theme: \(selectedTheme == .dark ? "Dark" : "Light")")
            Button("Switch Theme") {
                selectedTheme = (selectedTheme == .light) ? .dark : .light
            }
        }
        .preferredColorScheme(selectedTheme == .dark ? .dark : .light)
    }
}

解释:

  • selectedThemeRaw 存储枚举的原始值(String 类型)。
  • 提供了 selectedTheme 属性用于在 UI 和枚举数据之间转换。

示例 4:存储复杂对象(使用 JSON 编码) #

如果你需要存储复杂类型(如自定义对象),可以将它们转化为 DataString

import SwiftUI

struct User: Codable {
    var name: String
    var age: Int
}

// 自定义 User 类型的 AppStorage
@propertyWrapper
struct AppStorageCodable<T: Codable>: DynamicProperty {
    let key: String
    let defaultValue: T

    @AppStorage private var storedData: Data?

    var wrappedValue: T {
        get {
            if let data = storedData {
                return try! JSONDecoder().decode(T.self, from: data)
            }
            return defaultValue
        }
        set {
            storedData = try? JSONEncoder().encode(newValue)
        }
    }
}

struct ContentView: View {
    @AppStorageCodable("currentUser", defaultValue: User(name: "Guest", age: 0)) var currentUser

    var body: some View {
        VStack {
            Text("Name: \(currentUser.name)")
            Text("Age: \(currentUser.age)")

            Button("Update User") {
                currentUser = User(name: "John", age: 35) // 更新对象
            }
        }
    }
}

解释:

  • AppStorageCodable 是一个自定义的属性包装器,用于将 Codable 类型转化为 AppStorage 支持的 Data 格式,并存储到 UserDefaults
  • 自动对复杂数据(如 User 对象)进行序列化和反序列化。

@AppStorage 的注意事项 #

  1. 不要存储大量数据

    • @AppStorage 的底层依赖 UserDefaults,而 UserDefaults 更适合存储轻量级的配置(如布尔值、字符串、数字等),不推荐用来存储大数据(如图片或文件)。
  2. 避免密集操作

    • UserDefaults 不是高性能存储,每次写入/读取都需要一定的性能开销。如果需要频繁修改或大量存储,考虑使用 Core DataSQLite
  3. 跨设备同步 iCloud(可选)

    • 如果你希望 AppStorage 值在多台设备间同步,可以将存储的值绑定到共享的 UserDefaults 容器(如 iCloud):
      @AppStorage("username", store: UserDefaults(suiteName: "iCloud.com.example.myapp")) var username: String = "Guest"
      
    • ⚠️ 确保正确设置 App 的 iCloud 容器。
  4. 安全类型转换

    • 如果 @AppStorage 获取不到值(比如数据被外部清除),它会返回默认值(如果设置了)。确保在实现时正确定义默认值,防止应用崩溃。

和其他 SwiftUI 数据持久化方式的对比 #

1. 和 @SceneStorage 的区别 #

@SceneStorage 适用于每个不同 “场景”(即窗口)的独立状态存储,而 @AppStorage 是全局共享的(所有场景访问同一份数据)。

属性特性用途
@AppStorage持久化到 UserDefaults用于跨场景的设置或全局状态
@SceneStorage恢复场景级别状态适用于多窗口场景,各窗口状态独立

2. 和 UserDefaults 的区别 #

@AppStorage 是现代化的、类型安全的,和 SwiftUI 绑定。但是底层仍然依赖于 UserDefaults

操作类型UserDefaults 方法@AppStorage(更简洁)
存储用户偏好UserDefaults.standard.set()使用简单变量即可
监听和 UI 更新需要手动更新自动完成数据绑定和 UI 刷新

总结 #

  • AppStorage 简化了 UserDefaults 的使用,同时自动绑定到 SwiftUI 的视图更新机制。
  • 应用场景:存储轻量和重要的配置项,比如应用主题、用户语言、通知开关等。
  • 进阶用法:通过枚举、自定义类型等扩展其功能。

store 参数枚举 #

@AppStoragestore 参数用于指定数据存储的目标位置。它是一个可选的参数,默认使用的是 UserDefaults.standard,即当前 App 的默认用户偏好存储。虽然 store 接收的是一个 UserDefaults 实例,但 SwiftUI 并未直接提供与 store 参数关联的具体枚举,而是让开发者基于不同的 UserDefaults 实例来选择适当的存储路径。

以下是一般情况下,你可以通过 store 参数传递的几种常见实例选项(与 UserDefaults 相关)。


常用的 UserDefaults 实例选项 #

1. 默认存储:UserDefaults.standard #

  • 这是 UserDefaults 默认的实例,表示数据存储在当前 App 的沙盒内。
  • 如果未指定 store 参数,@AppStorage 会默认使用 UserDefaults.standard

示例: #

@AppStorage("username", store: .standard) var username: String = "Guest"

等同于下面的简写(因为 store: .standard 是默认值): #

@AppStorage("username") var username: String = "Guest"

当你不需要自定义存储路径,只希望以普通的沙盒方式存储用户偏好数据时,使用默认的 .standard


2. 共享存储:UserDefaults(suiteName:) #

  • 如果你的应用需要共享多个 App 或 App Extension 之间的数据,可以使用 App Groups 配置并提供 suiteName
  • store 参数可以指定一个共享的 UserDefaults 实例,比如:
    UserDefaults(suiteName: "group.com.example.myAppGroup")
    
  • 适用于跨 App 数据共享(例如 iOS 应用和其 Widget 之间)。

示例: #

@AppStorage("sharedKey", store: UserDefaults(suiteName: "group.com.example.myAppGroup"))
var sharedData: String = "Default Value"

关键点: #

  1. suiteName 必须匹配你在 Xcode 中启用的App Group Identifier
  2. 多个 App 和 Extension 中使用相同的 suiteName 会共享数据。

3. 临时存储:自定义 UserDefaults 实例 #

你也可以创建一个完全独立的 UserDefaults 实例,用于临时存储一些调试、测试或特定的非全局数据。

示例: #

let customDefaults = UserDefaults(suiteName: "custom.testing.defaults")

@AppStorage("testingKey", store: customDefaults) var tempData: Int = 0

在这种情况下,数据会存储在专用的 UserDefaults 容器中,而不会影响 .standard 或 App Group 的存储。


store 参数的可选性 #

如果不传入 store 参数:

  • 默认使用 UserDefaults.standard
  • 如果你需要切换到共享 App Group 存储,则必须通过 UserDefaults(suiteName:) 显式提供。

默认写法:

@AppStorage("someKey") var value: String = "DefaultValue"

等价于指定 .standard

@AppStorage("someKey", store: .standard) var value: String = "DefaultValue"

其他特殊场景 #

  1. 子线程共享存储 如果你在多线程环境下需要共享一个特定的存储实例,可以手动传递相应的 UserDefaults
  2. 对特殊沙盒路径的数据存储 如果你的数据需要存储在自定义的存储位置,比如调试时隔离某些数据,你也可以通过 suiteName 提供沙盒路径。

总结:用户可用 store 参数表示的列表 #

选项描述示例
.standard(默认值)App 的默认 UserDefaults 存储,针对当前应用范围内保存用户偏好数据。@AppStorage(key, store: .standard)
.suiteName("group.identifier")共享存储,允许多个 App 和扩展(Widget、App Extension 等)使用相同存储空间,需使用 App Groups 配置。@AppStorage(key, store: UserDefaults(suiteName: "group.yourAppGroupID"))
自定义 UserDefaults 实例用于创建单独的存储实例,可用于隔离测试、调试、或特定用例下的数据存储需求。UserDefaults(suiteName: "custom.storage")

SwiftUI 本身并未提供封装的枚举列表,而是直接继承了 UserDefaults 的灵活性,因此枚举选项需要来自 UserDefaults 本身。


实践建议 #

  • 如果没有特别需求,选择默认值 .standard 即可。
  • 如果需要多个 App 或扩展间共享数据,请配置 App Groups,并传入符合 suiteNameUserDefaults 实例。
  • 如果在调试场景或对数据隔离有强需求时,可以使用自定义 UserDefaults 实例。

这个设计让 @AppStorage 的可用性变得更加广泛,同时仍保持了 SwiftUI 与 UIKit 数据管理的互操作性。

本文共 2918 字,上次修改于 Jan 16, 2025