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?
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.
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.
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.
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) } } }
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")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With