Save and Load Custom Dictionary - NSUserDefaults

2020-07-18 02:02发布

问题:

I have an [Int:Bool] dictionary and I am trying to save it into my NSDictionary.. However, it crashes with error Attempt to set a non-property-list object

let dictionary = [Int:Bool]()


self.dictionary[2] = true
self.dictionary[3] = false

NSUserDefaults.standardUserDefaults().setObject(dictionary, forKey: "dictionary")

Also, for loading, first I tried this but error logged it strictly requires AnyObject?.

NSUserDefaults.standardUserDefaults().objectForKey("dictionary")

Then I tried this and it logged:

NSUserDefaults.standardUserDefaults().objectForKey("dictionary") as? [Int:Bool]

I also tried dictionaryForKey. I got..

NSUserDefaults.standardUserDefaults().dictionaryForKey("dictionary")

Cannot assign value to type [String: AnyObject] to type [Int:Bool]


So which one of these 2 is a better approach to take? (The values would be optional in my case I think)

1.
NSUserDefaults.standardUserDefaults().objectForKey("dictionary") as? [Int:Bool] ?? [Int:Bool]()

2.
NSUserDefaults.standardUserDefaults().objectForKey("dictionary") as? [Int:Bool])!

回答1:

Swift 4

Among basic types, UserDefaults can save any object that conforms to Codable protocol. Dictionary is one of the types that implements this protocol. You don't even need to write any custom code:

let dictionary = ["name": "Adam"]

// Save to User Defaults
UserDefaults.standard.set(dictionary, forKey: "names") 

// Read from User Defaults     
let saved = UserDefaults.standard.value(forKey: "names") as? [String: String]

See more info about Codable

Swift 3

You can use UserDefaults to save a Dictionary as long as key and value types are types that can be represented in a plist format (NSNumber, Data, etc.). If that's not the case, we can always serialise other types to Data when writing and deserialise from Data when reading. It can be accomplished with pretty simple extension of UserDefaults using NSKeyArchiver:

extension UserDefaults {
    /// Save dictionary on key
    open func set<Key, Value>(dictionary: [Key: Value]?, forKey key: String) {
        let data = NSKeyedArchiver.archivedData(withRootObject: dictionary as Any)
        set(data, forKey: key)
    }

    // Retrieve dictionary for key
    open func dictionary<Key, Value>(forKey key: String) -> [Key: Value]? {
        guard let data = object(forKey: key) as? Data else { return nil }
        return NSKeyedUnarchiver.unarchiveObject(with: data) as? [Key: Value]
    }
}

Now you can call these methods:

let ages = ["Adam": 25]

// Save
UserDefaults.standard.set(dictionary: ages, forKey: "ages")

// Read
let saved: [String: Int]? = UserDefaults.standard.dictionary(forKey: "ages")
print(saved) // Optional(["Adam": 25])

Swift 2

Save custom data

func setCustomDictionary(dict: [Int: Bool]) {
    let keyedArch = NSKeyedArchiver.archivedDataWithRootObject(dict)

    NSUserDefaults.standardUserDefaults().setObject(keyedArch, forKey: "dictionary")
}

Retrieve data

func getDictionary() -> [Int: Bool]? {
    let data = NSUserDefaults.standardUserDefaults().objectForKey("dict")
    let object = NSKeyedUnarchiver.unarchiveObjectWithData(data as! NSData)
    return object as? [Int: Bool]
}

Usage

var customDictionary = [Int: Bool]()
customDictionary[2] = true
customDictionary[3] = false

// Store data in NSUserDefaults
setCustomDictionary(customDictionary)

// Get data from NSUserDefaults
let userDefaultsDictionary = getDictionary()


回答2:

I had a similar issue, but with different types of data. My suggestion is to convert to NSData and retrieve the data like so:

/// Save
NSUserDefaults.standardUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(object), forKey: key)

/// Read
var data = NSUserDefaults.standardUserDefaults().objectForKey(key) as NSData
var object = NSKeyedUnarchiver.unarchiveObjectWithData(data) as [String: String]

(Although it is mentioned [String : String] I actually used for [[String: AnyObject]] and worked, so maybe it can work for you too!)



回答3:

This is for Swift 3

func setCustomDictionary(dict: [Int: Bool]) {
    let keyedArch = NSKeyedArchiver.archivedData(withRootObject: dict)

    UserDefaults.standard.set(keyedArch, forKey: "dictionary")
}

func getDictionary() -> [Int: Bool]? {
    let data = UserDefaults.standard.object(forKey: "dict")
    let object = NSKeyedUnarchiver.unarchiveObject(with: (data as! NSData) as Data)
    return object as? [Int: Bool]
}


回答4:

if you neeed more types you can use generics like this:

func saveUserDefaults<T>(withKey key: String, dict: AnyObject, myType: T.Type) {
    guard let dict = dict as? T else {
        print("Type mismatch")
        return
    }
    let archiver = NSKeyedArchiver.archivedData(withRootObject: dict)
    UserDefaults.standard.set(archiver, forKey: key)
}

func getUserDefaults<T>(withKey key: String, myType: T.Type) -> T? {
    let unarchivedObject = getUserDefaultData(withKey: key)
    return unarchivedObject as? T
}

func getUserDefaultData(withKey key: String) -> Any? {
    guard let data = UserDefaults.standard.object(forKey: key) else {
        return nil
    }
    guard let retrievedData = data as? Data else {
        return nil
    }
    return NSKeyedUnarchiver.unarchiveObject(with: retrievedData)
}

For example [Int:Int] type usage:

    var customDictionary = [Int: Int]()
    customDictionary[234] = 1
    customDictionary[24] = 2
    customDictionary[345] = 3

    saveUserDefaults(withKey: "hello", dict: customDictionary as AnyObject, myType: [Int: Int].self)
    let savedDictionary = getUserDefaults(withKey: "hello", myType: [Int: Int].self)
    print(savedDictionary)