Search code examples
swiftdictionaryproperty-wrapper

Swift: Loading and Saving with @propertyWrapper and an [String : Bool]


I have the following property wrapper to store my values:

@propertyWrapper struct UserDefault<T: Codable> {
    var key: String
    var wrappedValue: T? {
        get {
            if let data = UserDefaults.standard.object(forKey: key) as? Data {
                return try? JSONDecoder().decode(T.self, from: data)
            }
            return nil
        }
        set {
            if let encoded = try? JSONEncoder().encode(newValue) {
                UserDefaults.standard.set(encoded, forKey: key)
            }
        }
    }
}

I store my values in the following Class:

class AppState: ObservableObject {
    @UserDefault(key: "achievements") var achievements: [String : Bool]?
}

In my Struct I try to read values, but I always get nil. What am I missing?

struct XXX {
   AppState().achievements             = [:]
   AppState().achievements?["Normal"] = true
   print("ACHIEVEMENTS")
   print(AppState().achievements)
}

With a ? I get nil, when I try to unwrap this value the app crashes. What do I need to do to write and read from my achievement variable?


Solution

  • Aside from the structural issues (you appear to be putting bare expressions in the body of a struct, when these should be in a func), the issue comes down to not referring to the same instance of AppState when creating the achievements initially.

    Everytime you call AppState(), you're creating a new instance.

    func testWithProblem() {
       // Creating an instance of AppState and setting achievements...
       AppState().achievements = [:]
       // Creating another instance! The first instance is now gone from memory.
       // achievements on this instance is nil, so the next line has no effect.
       AppState().achievements?["Normal"] = true
       print("ACHIEVEMENTS")
       // Yet another instance...
       print(AppState().achievements)
    }
    

    You make sure you're referring to the same instance.

    func testItWorksNow() {
       // You need to use the same instance...
       let appState = AppState()
       appState.achievements = [:]
       // So this line will refer to the same achievements dict as you've just created.
       appState.achievements?["Normal"] = true
       print("ACHIEVEMENTS")
       print(appState.achievements)
    }