Swift:使用@propertyWrapper读写[String:Bool]时返回nil的问题排查
问题分析
你遇到的问题有两个核心原因:
- @PropertyWrapper 的类型不匹配:你的
UserDefault包装器中,wrappedValue是T?类型,但存储时编码的是Optional<T>,读取时却尝试解码为T,导致解码失败始终返回nil。 - 每次创建新的 AppState 实例:在结构体
XXX中,你每次调用AppState()都会生成全新的实例,且赋值操作逻辑有误(对nil的字典进行修改不会生效)。
修复步骤
1. 修正 @PropertyWrapper 的类型逻辑
调整 UserDefault 实现,确保存储和读取的类型一致,以下是两种可选方案:
方案一:保持 wrappedValue 为可选类型
修改解码逻辑,解码为 T? 而非 T,同时新增 nil 值时清理对应键的逻辑:
@propertyWrapper struct UserDefault<T: Codable> { var key: String var wrappedValue: T? { get { guard let data = UserDefaults.standard.object(forKey: key) as? Data else { return nil } return try? JSONDecoder().decode(T?.self, from: data) } set { guard let encoded = try? JSONEncoder().encode(newValue) else { UserDefaults.standard.removeObject(forKey: key) return } UserDefaults.standard.set(encoded, forKey: key) } } }
方案二:使用非可选类型并支持默认值(更推荐)
如果业务场景需要默认值,可将包装器改为非可选类型,避免处理 nil 的繁琐:
@propertyWrapper struct UserDefault<T: Codable> { var key: String var defaultValue: T var wrappedValue: T { get { guard let data = UserDefaults.standard.object(forKey: key) as? Data else { return defaultValue } return try? JSONDecoder().decode(T.self, from: data) ?? defaultValue } set { guard let encoded = try? JSONEncoder().encode(newValue) else { return } UserDefaults.standard.set(encoded, forKey: key) } } init(key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } }
对应 AppState 的定义修改为:
class AppState: ObservableObject { @UserDefault(key: "achievements", defaultValue: [:]) var achievements: [String : Bool] }
2. 正确使用 AppState 实例
由于 AppState 是 ObservableObject,需要共享同一个实例,而非每次创建新对象,以下是两种常见实现方式:
方式一:单例模式
修改 AppState 为单例,确保全局唯一实例:
class AppState: ObservableObject { static let shared = AppState() @UserDefault(key: "achievements") var achievements: [String : Bool]? }
在结构体 XXX 中使用单例:
struct XXX { func updateAchievements() { // 初始化空字典(如果之前未存储) if AppState.shared.achievements == nil { AppState.shared.achievements = [:] } AppState.shared.achievements?["Normal"] = true print("ACHIEVEMENTS") print(AppState.shared.achievements) } }
方式二:SwiftUI 环境对象传递(SwiftUI 项目适用)
在 App 入口注入 AppState:
@main struct YourApp: App { @StateObject private var appState = AppState() var body: some Scene { WindowGroup { ContentView() .environmentObject(appState) } } }
在子视图/结构体中通过环境获取实例:
struct XXX: View { @EnvironmentObject var appState: AppState var body: some View { Button("Update Achievements") { if appState.achievements == nil { appState.achievements = [:] } appState.achievements?["Normal"] = true print("ACHIEVEMENTS") print(appState.achievements) } } }
额外提示
如果使用方案二的非可选类型,无需处理 nil 情况,直接修改字典即可:appState.achievements["Normal"] = true,代码会更简洁。
内容的提问来源于stack exchange,提问作者Nathanael Tse
相关产品推荐
相关产品推荐

