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 应用的重要技能!