Swift 3 saving and retrieving custom object from userDefaults

user773881 picture user773881 · Jun 23, 2016 · Viewed 61.3k times · Source

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?

Answer

Leo Dabus picture Leo Dabus · Jun 23, 2016

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()
        // setting a value for a key
        do {
            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")
                
        } catch {
            print(error)
        }
        
        // retrieving a value for a key
        do {
            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
            } else {
                print("There is an issue")
            }
        } catch {
            print(error)
        }
    }
}