SwiftUI UserDefaults 用户默认值学习笔记

143 阅读5分钟

SwiftUI UserDefaults 用户默认值学习笔记

什么是 UserDefaults

UserDefaults 是 iOS 系统提供的一个轻量级数据持久化机制,用于存储用户的偏好设置和应用配置。数据存储在设备本地,应用重启后依然保持。

核心概念

数据持久化

  • 临时数据:存在内存中,应用关闭后消失
  • 持久化数据:存储在磁盘上,应用重启后仍然存在
  • UserDefaults:轻量级持久化,适合存储设置和偏好

存储位置

/Users/用户名/Library/Developer/CoreSimulator/Devices/设备ID/
data/Containers/Data/Application/应用ID/Library/Preferences/应用Bundle.plist

基本用法

1. 存储数据

let userDefaults = UserDefaults.standard

// 存储基本数据类型
userDefaults.set("张三", forKey: "username")
userDefaults.set(25, forKey: "age")
userDefaults.set(true, forKey: "isFirstLaunch")
userDefaults.set(3.14, forKey: "score")

// 存储复杂类型
userDefaults.set(["苹果", "香蕉"], forKey: "fruits")
userDefaults.set(["name": "张三", "age": 25], forKey: "userInfo")

2. 读取数据

// 读取数据(需要提供默认值)
let username = userDefaults.string(forKey: "username") ?? "默认用户名"
let age = userDefaults.integer(forKey: "age")  // 默认返回 0
let isFirstLaunch = userDefaults.bool(forKey: "isFirstLaunch")  // 默认返回 false
let score = userDefaults.double(forKey: "score")  // 默认返回 0.0

// 读取复杂类型
let fruits = userDefaults.array(forKey: "fruits") as? [String] ?? []
let userInfo = userDefaults.dictionary(forKey: "userInfo") ?? [:]

3. 删除数据

// 删除指定键的数据
userDefaults.removeObject(forKey: "username")

// 立即同步(通常不需要手动调用)
userDefaults.synchronize()

SwiftUI 中的使用

1. 基本集成

class SettingsStore: ObservableObject {
    private let userDefaults = UserDefaults.standard
    
    @Published var username: String {
        didSet {
            userDefaults.set(username, forKey: "username")
        }
    }
    
    @Published var isDarkMode: Bool {
        didSet {
            userDefaults.set(isDarkMode, forKey: "isDarkMode")
        }
    }
    
    init() {
        // 从 UserDefaults 加载数据
        self.username = userDefaults.string(forKey: "username") ?? ""
        self.isDarkMode = userDefaults.bool(forKey: "isDarkMode")
    }
}

2. @AppStorage 属性包装器(iOS 14+)

struct SettingsView: View {
    // 直接绑定到 UserDefaults,自动同步
    @AppStorage("username") private var username: String = ""
    @AppStorage("age") private var age: Int = 0
    @AppStorage("isDarkMode") private var isDarkMode: Bool = false
    @AppStorage("selectedTheme") private var selectedTheme: String = "system"
    
    var body: some View {
        Form {
            TextField("用户名", text: $username)
            Stepper("年龄: (age)", value: $age, in: 0...100)
            Toggle("深色模式", isOn: $isDarkMode)
            Picker("主题", selection: $selectedTheme) {
                Text("跟随系统").tag("system")
                Text("浅色").tag("light")
                Text("深色").tag("dark")
            }
        }
    }
}

支持的数据类型

原生支持的类型

// 基本类型
userDefaults.set("字符串", forKey: "string")
userDefaults.set(42, forKey: "integer")
userDefaults.set(3.14, forKey: "double")
userDefaults.set(true, forKey: "bool")

// 集合类型
userDefaults.set([1, 2, 3], forKey: "array")
userDefaults.set(["key": "value"], forKey: "dictionary")

// Foundation 类型
userDefaults.set(Date(), forKey: "date")
userDefaults.set("hello".data(using: .utf8), forKey: "data")

自定义类型(需要编码)

// 遵循 Codable 的自定义类型
struct User: Codable {
    let name: String
    let age: Int
    let email: String
}

// 存储自定义类型
func saveUser(_ user: User) {
    if let encoded = try? JSONEncoder().encode(user) {
        userDefaults.set(encoded, forKey: "user")
    }
}

// 读取自定义类型
func loadUser() -> User? {
    guard let data = userDefaults.data(forKey: "user"),
          let user = try? JSONDecoder().decode(User.self, from: data) else {
        return nil
    }
    return user
}

最佳实践

1. 使用常量管理键名

struct UserDefaultsKeys {
    static let username = "username"
    static let isDarkMode = "isDarkMode"
    static let lastLoginDate = "lastLoginDate"
    static let appVersion = "appVersion"
}

// 使用
userDefaults.set("张三", forKey: UserDefaultsKeys.username)

2. 创建 UserDefaults 扩展

extension UserDefaults {
    var username: String {
        get { string(forKey: UserDefaultsKeys.username) ?? "" }
        set { set(newValue, forKey: UserDefaultsKeys.username) }
    }
    
    var isDarkMode: Bool {
        get { bool(forKey: UserDefaultsKeys.isDarkMode) }
        set { set(newValue, forKey: UserDefaultsKeys.isDarkMode) }
    }
    
    var lastLoginDate: Date? {
        get { object(forKey: UserDefaultsKeys.lastLoginDate) as? Date }
        set { set(newValue, forKey: UserDefaultsKeys.lastLoginDate) }
    }
}

// 使用
UserDefaults.standard.username = "新用户名"
let currentUser = UserDefaults.standard.username

3. 封装设置管理器

class SettingsManager: ObservableObject {
    static let shared = SettingsManager()
    
    private let userDefaults = UserDefaults.standard
    
    @Published var username: String {
        didSet { userDefaults.username = username }
    }
    
    @Published var isDarkMode: Bool {
        didSet { userDefaults.isDarkMode = isDarkMode }
    }
    
    private init() {
        self.username = userDefaults.username
        self.isDarkMode = userDefaults.isDarkMode
    }
    
    func resetToDefaults() {
        username = ""
        isDarkMode = false
    }
}

适用场景

✅ 适合存储的数据

  • 用户偏好设置(主题、语言、字体大小)
  • 应用配置(API端点、调试标志)
  • 简单的用户信息(用户名、邮箱)
  • 应用状态(是否首次启动、版本号)
  • 轻量级缓存数据

❌ 不适合存储的数据

  • 大量数据(图片、视频、大文件)
  • 敏感信息(密码、私钥)
  • 复杂的关系型数据
  • 需要查询和索引的数据
  • 临时数据

常见错误

1. 忘记提供默认值

// ❌ 错误:可能返回 nil
let username = userDefaults.string(forKey: "username")

// ✅ 正确:提供默认值
let username = userDefaults.string(forKey: "username") ?? "Guest"

2. 键名拼写错误

// ❌ 错误:容易拼写错误
userDefaults.set("value", forKey: "userName")
let value = userDefaults.string(forKey: "username")  // 拼写不一致

// ✅ 正确:使用常量
struct Keys {
    static let userName = "userName"
}
userDefaults.set("value", forKey: Keys.userName)
let value = userDefaults.string(forKey: Keys.userName)

3. 存储过大的数据

// ❌ 避免:存储大量数据
let largeArray = Array(1...1000000)
userDefaults.set(largeArray, forKey: "largeData")

// ✅ 推荐:使用文件系统或数据库
// 大量数据应该使用 Core Data、SQLite 或文件存储

4. 过度使用 synchronize()

// ❌ 不必要:过度调用 synchronize
userDefaults.set("value1", forKey: "key1")
userDefaults.synchronize()
userDefaults.set("value2", forKey: "key2")
userDefaults.synchronize()

// ✅ 正确:系统会自动同步,通常不需要手动调用
userDefaults.set("value1", forKey: "key1")
userDefaults.set("value2", forKey: "key2")
// 系统会在适当时机自动同步

性能考虑

1. 读取性能

// UserDefaults 的读取是同步的,但很快
// 适合存储小量数据的频繁读取
let setting = userDefaults.bool(forKey: "someSetting")

2. 写入性能

// 写入操作会被缓存,批量写入时性能更好
userDefaults.set("value1", forKey: "key1")
userDefaults.set("value2", forKey: "key2")
userDefaults.set("value3", forKey: "key3")
// 系统会批量写入磁盘

3. 内存使用

// UserDefaults 会将数据缓存在内存中
// 避免存储过大的数据集合

调试和测试

1. 查看 UserDefaults 内容

// 打印所有 UserDefaults 数据
print(UserDefaults.standard.dictionaryRepresentation())

// 检查特定键是否存在
if UserDefaults.standard.object(forKey: "someKey") != nil {
    print("键存在")
}

2. 单元测试

// 使用临时 UserDefaults 进行测试
let testUserDefaults = UserDefaults(suiteName: "test")!

func testSettings() {
    testUserDefaults.set("testValue", forKey: "testKey")
    let value = testUserDefaults.string(forKey: "testKey")
    XCTAssertEqual(value, "testValue")
    
    // 清理测试数据
    testUserDefaults.removePersistentDomain(forName: "test")
}

3. 重置 UserDefaults

// 重置所有 UserDefaults 数据
func resetUserDefaults() {
    let domain = Bundle.main.bundleIdentifier!
    UserDefaults.standard.removePersistentDomain(forName: domain)
    UserDefaults.standard.synchronize()
}

总结

UserDefaults 是 SwiftUI 应用中处理用户偏好设置的理想工具:

优势

简单易用:API简洁,易于理解
自动持久化:数据自动保存到磁盘
系统集成:与系统设置应用集成良好
性能优秀:读写速度快,内存占用小

限制

数据类型限制:只支持特定的数据类型
存储容量:不适合大量数据存储
安全性:数据未加密,不适合敏感信息
跨应用:默认情况下数据不能跨应用共享

使用原则

  • 只存储轻量级的配置和偏好数据
  • 使用 @AppStorage 简化 SwiftUI 集成
  • 通过常量或扩展管理键名
  • 为复杂数据类型实现 Codable
  • 在合适的场景下选择 UserDefaults

掌握 UserDefaults 是构建用户友好的 SwiftUI 应用的重要技能!