Search code examples
swiftdictionarystructurensuserdefaults

Swift: How can I save and load a dictionary with values of a structure in Userdefaults


I've read some posts on the subject on StackOverflow. As far as I understood it, I can't save every object in Swift immediately in the userdefaults. In the first step I tried to convert my dictionary to an NSData object, but already at this point, I fail. What am I doing wrong? Thanks for your help!

I want to manage app preferences in the mentioned dictionary. The dictionary with the value of a struct would be the best solution for me.

import Foundation
import UIKit


struct Properties: Codable {
    var v1: String
    var v2: Int
    var v3: Bool
}


let userdefaults = UserDefaults.standard

var dictStruct: Dictionary<String, Properties> = [:]

dictStruct["a"] = Properties(v1: "AAA", v2: 0, v3: false)
dictStruct["b"] = Properties(v1: "BBB", v2: 1, v3: true)


let dataEncoded: Data = try NSKeyedArchiver.archivedData(withRootObject: dictStruct, requiringSecureCoding: false)

userdefaults.set(dataEncoded, forKey: "KeyData")

let dataDecoded = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(userdefaults.object(forKey: "KeyData") as! Data) as! Dictionary<String, Properties>

print(dataDecoded["a"]!.v1)

Playground execution terminated: An error was thrown and was not caught: Error Domain=NSCocoaErrorDomain Code=4866 "Caught exception during archival: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600000fc73c0 ...


Solution

  • You are mixing up Codable and NSCoding. NSKeyed(Un)Archiver belongs to NSCoding. Don't use it.

    The proper API for Codable is PropertyListEncoder/-Decoder

    struct Properties: Codable {
        var v1: String
        var v2: Int
        var v3: Bool
    }
    
    let userdefaults = UserDefaults.standard
    
    var dictStruct: Dictionary<String, Properties> = [:]
    
    dictStruct["a"] = Properties(v1: "AAA", v2: 0, v3: false)
    dictStruct["b"] = Properties(v1: "BBB", v2: 1, v3: true)
    
    do {
        let dataEncoded = try PropertyListEncoder().encode(dictStruct)
    
        userdefaults.set(dataEncoded, forKey: "KeyData")
    
        if let data = userdefaults.data(forKey: "KeyData") {
           let dataDecoded = try PropertyListDecoder().decode([String:Properties].self, from: data)
           print(dataDecoded["a"]!.v1)
        }
    } catch { print(error) }