You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 10:10:34