核心速览:@AppStorage 原生不支持 Date 或 Array。通过让自定义类型(或扩展系统类型)遵循 RawRepresentable 协议,并将其 RawValue 映射为 String 或 Data,即可轻松突破这一限制。
背景
@AppStorage 是 SwiftUI 提供的属性包装器,它是 UserDefaults 的优雅封装。然而,默认情况下,它仅支持 Bool, Int, Double, String, URL, Data 等基础类型。当开发者尝试存储 Date、数组 ([String]) 或自定义结构体时,编译器会直接报错。
解决方案
虽然 @AppStorage 不直接支持复杂类型,但它支持任何符合 RawRepresentable 协议且 RawValue 为 Int 或 String 的类型。我们可以利用 JSON 编解码,将复杂对象转换为 String,从而“欺骗” @AppStorage 完成存储。
以下是针对 Swift 6 环境的实现方案(注意 @retroactive 的使用)。
1. 支持 Date 类型
将 Date 编码为 JSON 字符串存储。
Swift
// Swift 6: 使用 @retroactive 消除追溯一致性警告
extension Date: @retroactive RawRepresentable {
public typealias RawValue = String
public init?(rawValue: RawValue) {
guard let data = rawValue.data(using: .utf8),
let date = try? JSONDecoder().decode(Date.self, from: data) else {
return nil
}
self = date
}
public var rawValue: RawValue {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8) else {
return ""
}
return result
}
}
// 使用
struct DateView: View {
@AppStorage("lastLogin") var lastLogin = Date()
// ...
}
2. 支持数组 (Array)
通过泛型扩展,让所有元素符合 Codable 的数组都支持 @AppStorage。
Swift
// Swift 6: 使用 @retroactive 明确意图
extension Array: @retroactive RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data) else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8) else {
return "[]"
}
return result
}
}
// 使用
struct ListView: View {
@AppStorage("savedIds") var ids = [1, 2, 3]
@AppStorage("history") var history: [String] = []
// ...
}
3. 支持自定义结构体
对于自定义结构体,无需扩展,直接声明遵循 RawRepresentable 即可。
Swift
struct UserSettings: Codable, RawRepresentable {
var isDarkMode: Bool
var username: String
// 样板代码:实现 RawRepresentable 将自身转为 String
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(UserSettings.self, from: data) else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8) else {
return "{}"
}
return result
}
}
// 使用
@AppStorage("settings") var settings = UserSettings(isDarkMode: false, username: "Guest")
注意事项
- 性能开销:每次读写都会触发 JSON 编解码。对于存储大量数据的数组,请谨慎使用此方法,以免阻塞主线程(
@AppStorage的读取通常在主线程)。 - Swift 6 兼容性:在 Swift 6 中,对标准库类型(如
Date、Array)进行追溯一致性(Retroactive Conformance)扩展会产生警告。建议使用@retroactive关键字(如上例所示)来显式声明,或封装一个 Wrapper 结构体来避免污染全局命名空间。 - 默认值:如果 JSON 解码失败,
@AppStorage将回退到你提供的默认值。