Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift saving and retrieving custom object from UserDefaults

I have this in Playground using Swift 3, Xcode 8.0:

import Foundation class Person: NSObject, NSCoding {     var name: String     var age: Int     init(name: String, age: Int) {         self.name = name         self.age = age     }     required convenience init(coder aDecoder: NSCoder) {         let name = aDecoder.decodeObject(forKey: "name") as! String         let age = aDecoder.decodeObject(forKey: "age") as! Int         self.init(             name: name,             age: age         )     }     func encode(with aCoder: NSCoder) {         aCoder.encode(name, forKey: "name")         aCoder.encode(age, forKey: "age")     } } 

create array of Person

let newPerson = Person(name: "Joe", age: 10) var people = [Person]() people.append(newPerson) 

encode the array

let encodedData = NSKeyedArchiver.archivedData(withRootObject: people) print("encodedData: \(encodedData))") 

save to userDefaults

let userDefaults: UserDefaults = UserDefaults.standard() userDefaults.set(encodedData, forKey: "people") userDefaults.synchronize() 

check

print("saved object: \(userDefaults.object(forKey: "people"))") 

retreive from userDefaults

if let data = userDefaults.object(forKey: "people") {     let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: data as! Data)     print("myPeopleList: \(myPeopleList)") }else{     print("There is an issue") } 

just check the archived data

if let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: encodedData){    print("myPeopleList: \(myPeopleList)") }else{    print("There is an issue") } 

I'm not able to correctly save the data object to userDefaults, and in addition, the check at the bottom creates the error:

Fatal error: Unexpectedly found nil while unwrapping an Optional value

The "check" line also shows the saved object is nil. Is this an error in my object's NSCoder?

like image 431
user773881 Avatar asked Jun 23 '16 00:06

user773881


People also ask

Can we store custom object in UserDefaults?

UserDefaults provides us with direct functions for storing primitive data types like Int, Double, Bool, and String. But for custom data types, there is no direct way to store them in UserDefaults.

How do you save an array of custom objects in UserDefaults?

Just use JSONEncoder to encode your collection and write the resulting data to disk. You can save it to your application support directory. Note that you should not use value(forKey:) method.

Can UserDefaults save image in Swift?

It is, but it isn't possible to store an image as is in the user's defaults database. The defaults system only supports strings, numbers, Date objects, and Data objects. This means that you need to convert the image to a Data object before you can store it in the user's defaults database.


2 Answers

Swift 4 or later

You can once again save/test your values in a Playground


UserDefaults need to be tested in a real project. Note: No need to force synchronize. If you want to test the coding/decoding in a playground you can save the data to a plist file in the document directory using the keyed archiver. You need also to fix some issues in your class:


class Person: NSObject, NSCoding {     let name: String     let age: Int     init(name: String, age: Int) {         self.name = name         self.age = age     }     required init(coder decoder: NSCoder) {         self.name = decoder.decodeObject(forKey: "name") as? String ?? ""         self.age = decoder.decodeInteger(forKey: "age")     }     func encode(with coder: NSCoder) {         coder.encode(name, forKey: "name")         coder.encode(age, forKey: "age")     } } 

Testing:

class ViewController: UIViewController {     override func viewDidLoad() {         super.viewDidLoad()          do {             // setting a value for a key             let newPerson = Person(name: "Joe", age: 10)             var people = [Person]()             people.append(newPerson)             let encodedData = try NSKeyedArchiver.archivedData(withRootObject: people, requiringSecureCoding: false)             UserDefaults.standard.set(encodedData, forKey: "people")             // retrieving a value for a key             if let data = UserDefaults.standard.data(forKey: "people"),                 let myPeopleList = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Person] {                 myPeopleList.forEach({print($0.name, $0.age)})  // Joe 10             }                             } catch {             print(error)         }              } } 
like image 173
Leo Dabus Avatar answered Sep 20 '22 22:09

Leo Dabus


let age = aDecoder.decodeObject(forKey: "age") as! Int 

This has been changed for Swift 3; this no longer works for value types. The correct syntax is now:

let age = aDecoder.decodeInteger(forKey: "age") 

There are associated decode...() functions for various different types:

let myBool = aDecoder.decodeBoolean(forKey: "myStoredBool") let myFloat = aDecoder.decodeFloat(forKey: "myStoredFloat") 

Edit: Full list of all possible decodeXXX functions in Swift 3

Edit:

Another important note: If you have previously saved data that was encoded with an older version of Swift, those values must be decoded using decodeObject(), however once you re-encode the data using encode(...) it can no longer be decoded with decodeObject() if it's a value type. Therefore Markus Wyss's answer will allow you to handle the case where the data was encoded using either Swift version:

self.age = aDecoder.decodeObject(forKey: "age") as? Int ?? aDecoder.decodeInteger(forKey: "age") 
like image 39
ibuprofane Avatar answered Sep 20 '22 22:09

ibuprofane