本篇目标

理解 UserDefaults 的用途与限制,掌握基本类型的存储、读取、删除,学会封装 UserDefaults 管理类,能正确处理默认值与数据类型。

一、UserDefaults 是什么

UserDefaults 是 iOS 提供的轻量级键值存储,用于保存用户偏好和少量数据。

适合存储:

  • 用户设置(主题、字号、语言)

  • 开关状态(是否开启通知)

  • 简单标记(是否首次启动、是否已登录)

  • 少量用户信息(用户名、昵称)

不适合存储:

  • 大量数据(图片、文件、列表)

  • 敏感信息(密码、Token)

  • 复杂对象(应使用文件或数据库)

特点:

  • 基于 plist 文件存储。

  • 数据自动持久化。

  • 读写速度快。

  • 不需要手动保存。

  • 应用重启后数据仍在。

二、基础读写

存储:

UserDefaults.standard.set("Tom", forKey: "username")
UserDefaults.standard.set(25, forKey: "age")
UserDefaults.standard.set(true, forKey: "isFirstLaunch")
UserDefaults.standard.set(1.75, forKey: "height")

读取:

let username = UserDefaults.standard.string(forKey: "username")
let age = UserDefaults.standard.integer(forKey: "age")
let isFirstLaunch = UserDefaults.standard.bool(forKey: "isFirstLaunch")
let height = UserDefaults.standard.double(forKey: "height")

对应读取方法:

存储类型

读取方法

默认值

String

string(forKey:)

nil

Int

integer(forKey:)

0

Bool

bool(forKey:)

false

Double

double(forKey:)

0.0

Float

float(forKey:)

0.0

Data

data(forKey:)

nil

Array

array(forKey:)

nil

Dictionary

dictionary(forKey:)

nil

注意:

  • string(forKey:) 返回可选类型。

  • integer(forKey:) 等返回非可选类型,不存在时返回默认值。

  • 类型不匹配时读取可能失败。

三、删除与检查

删除单个键:

UserDefaults.standard.removeObject(forKey: "username")

检查键是否存在:

if UserDefaults.standard.object(forKey: "username") != nil {
    print("存在")
}

注意:

  • 不能通过 string(forKey:) == nil 判断,因为存储空字符串也会返回空字符串。

  • 用 object(forKey:) 判断更准确。

删除所有数据:

if let domain = Bundle.main.bundleIdentifier {
    UserDefaults.standard.removePersistentDomain(forName: domain)
}

慎用:会清除应用所有 UserDefaults 数据。

四、默认值处理

使用 `??` 提供默认值:

let username = UserDefaults.standard.string(forKey: "username") ?? "Guest"

注册默认值:

UserDefaults.standard.register(defaults: [
    "username": "Guest",
    "fontSize": 14,
    "isDarkMode": false
])

let username = UserDefaults.standard.string(forKey: "username")  // Guest

说明:

  • register 注册的默认值不写入磁盘。

  • 只在键不存在时生效。

  • 适合应用启动时初始化默认配置。

五、存储数组与字典

存储数组:

let fruits = ["apple", "banana", "orange"]
UserDefaults.standard.set(fruits, forKey: "fruits")

let saved = UserDefaults.standard.stringArray(forKey: "fruits")

存储字典:

let settings = ["theme": "dark", "fontSize": 14]
UserDefaults.standard.set(settings, forKey: "settings")

let savedSettings = UserDefaults.standard.dictionary(forKey: "settings")

注意:

  • 数组和字典元素必须是 plist 支持的类型。

  • 支持类型:String、Int、Double、Bool、Date、Data、Array、Dictionary。

六、存储自定义对象

UserDefaults 不能直接存储自定义对象,需要先编码。

使用 Codable + JSON:

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

// 存储
let user = User(name: "Tom", age: 25, isVIP: true)
if let data = try? JSONEncoder().encode(user) {
    UserDefaults.standard.set(data, forKey: "currentUser")
}

// 读取
if let data = UserDefaults.standard.data(forKey: "currentUser"),
   let savedUser = try? JSONDecoder().decode(User.self, from: data) {
    print(savedUser.name)
}

说明:

  • Codable 让对象可以编码和解码。

  • 存储的是 Data 类型。

  • 读取时解码回对象。

七、封装 UserDefaults 管理类

直接使用 UserDefaults 键名容易出错,建议封装。

定义 Keys:

enum UserDefaultsKeys {
    static let username = "username"
    static let fontSize = "fontSize"
    static let isDarkMode = "isDarkMode"
    static let isFirstLaunch = "isFirstLaunch"
}

封装管理类:

class AppSettings {
    static let shared = AppSettings()
    private let defaults = UserDefaults.standard

    private init() {
        defaults.register(defaults: [
            UserDefaultsKeys.username: "Guest",
            UserDefaultsKeys.fontSize: 14,
            UserDefaultsKeys.isDarkMode: false,
            UserDefaultsKeys.isFirstLaunch: true
        ])
    }

    var username: String {
        get { defaults.string(forKey: UserDefaultsKeys.username) ?? "Guest" }
        set { defaults.set(newValue, forKey: UserDefaultsKeys.username) }
    }

    var fontSize: Int {
        get { defaults.integer(forKey: UserDefaultsKeys.fontSize) }
        set { defaults.set(newValue, forKey: UserDefaultsKeys.fontSize) }
    }

    var isDarkMode: Bool {
        get { defaults.bool(forKey: UserDefaultsKeys.isDarkMode) }
        set { defaults.set(newValue, forKey: UserDefaultsKeys.isDarkMode) }
    }

    var isFirstLaunch: Bool {
        get { defaults.bool(forKey: UserDefaultsKeys.isFirstLaunch) }
        set { defaults.set(newValue, forKey: UserDefaultsKeys.isFirstLaunch) }
    }
}

使用:

AppSettings.shared.username = "Tom"
print(AppSettings.shared.username)  // Tom

优点:

  • 键名集中管理。

  • 类型安全。

  • 默认值统一注册。

  • 便于维护和修改。

八、属性包装器封装

可以用 @propertyWrapper 简化。

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get {
            UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

enum Settings {
    @UserDefault(key: "username", defaultValue: "Guest")
    static var username: String

    @UserDefault(key: "fontSize", defaultValue: 14)
    static var fontSize: Int

    @UserDefault(key: "isDarkMode", defaultValue: false)
    static var isDarkMode: Bool
}

Settings.username = "Tom"
print(Settings.username)  // Tom

优点:

  • 语法简洁。

  • 类型安全。

  • 可读性高。

注意:

  • 属性包装器需要泛型支持。

  • 存储复杂类型时注意可选性。

九、与 SwiftUI 结合

@AppStorage:SwiftUI 提供的属性包装器,底层使用 UserDefaults。

struct SettingsView: View {
    @AppStorage("username") private var username = "Guest"
    @AppStorage("isDarkMode") private var isDarkMode = false
    @AppStorage("fontSize") private var fontSize = 14

    var body: some View {
        Form {
            TextField("用户名", text: $username)
            Toggle("深色模式", isOn: $isDarkMode)
            Stepper("字号:\(fontSize)", value: $fontSize, in: 12...24)
        }
    }
}

特点:

  • 自动读写 UserDefaults。

  • 值变化时自动刷新界面。

  • 支持 String、Int、Double、Bool、URL、Data。

指定存储:

@AppStorage("username", store: UserDefaults(suiteName: "group.com.example"))
private var username = "Guest"

用于 App Group 共享:适合主 App 与扩展之间共享数据。

十、观察变化

监听单个键:

NotificationCenter.default.addObserver(
    forName: UserDefaults.didChangeNotification,
    object: nil,
    queue: .main
) { _ in
    print("UserDefaults 发生变化")
}

注意:

  • didChangeNotification 是整个 UserDefaults 变化。

  • 不能精确到具体键。

  • 真实项目中,通常用 @AppStorage 或 KVO。

十一、使用注意事项

注意事项:

  • 不要存敏感信息。

  • 不要存大量数据。

  • 键名要有命名规范。

  • 数据大小控制在几百 KB 以内。

  • 应用卸载后 UserDefaults 数据会被清除。

十二、避坑指南

  • 敏感信息不要存:密码、Token 应使用 Keychain。

  • 不要存大量数据:大数组、图片用文件或数据库。

  • 不要用 string(forKey:) 判断存在:用 object(forKey:)。

  • 键名要统一管理:避免硬编码字符串。

  • 默认值要注册:否则第一次读取可能为空。

  • 类型要一致:存 String 读 String,不要混用。

  • @AppStorage 只支持基本类型:自定义对象需要编码。

十三、检查清单

  • [ ] 理解 UserDefaults 的用途与限制

  • [ ] 会存储和读取基本类型

  • [ ] 会删除和检查键

  • [ ] 会处理默认值

  • [ ] 能存储数组和字典

  • [ ] 能存储自定义对象(Codable)

  • [ ] 会封装 UserDefaults 管理类

  • [ ] 会使用 @AppStorage

  • [ ] 了解 UserDefaults 的性能边界