NSUserDefaults Loading Issue - ios

Whenever I open my app, it doesn't load my array values because the != nil function isn't called. Is there anything I can do about this?
Code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var toDoData = NSUserDefaults.standardUserDefaults()
if (toDoData.valueForKey("TDDATA") != nil){
todos = toDoData.valueForKey("TDDATA") as! NSArray as! [TodoModel]
}
if todos.count != 0{
toDoData.setValue(todos, forKeyPath: "TDDATA")
toDoData.synchronize()
}
}
Don't worry about the table. It populates perfectly. I just need the loading data issue fixed.
Code included in your answer helps a lot!
Thanks.
UPDATE:
Here is the TodoModel:
import Foundation
import UIKit
class TodoModel : NSObject, NSCoding {
var id: String
var image: String
var title: String
var desc: String
var scores: String
init (id: String, image: String, title: String, desc: String, scores: String) {
self.id = id
self.image = image
self.title = title
self.desc = desc
self.scores = scores
}
}

valueForKey and setValue:forKeyPath are KVC (Key Value Coding) methods (read here and here). It will not help you read/write to the user defaults database.
Looking in the NSUserDefaults documentation, there are a number of methods available for getting and setting values in the defaults database. Since you are using arrays, we will use:
arrayForKey to get.
setObject:forKey to set. (There is no array-specific setter)
EDIT: Try this in your viewDidAppear. Here we check if we have data, and if we do, we store it. If we don't have data, then check if the defaults database has some saved. If it does, use it instead. It would be advantageous to only load data from the defaults database in viewDidLoad, and then save in viewDidAppear or even better, a function which is called when a todo is added.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let defaults = NSUserDefaults.standardUserDefaults()
if todos.count > 0 {
// Save what we have
let data = NSKeyedArchiver.archivedDataWithRootObject(todos)
defaults.setObject(data, forKey: "TDDATA")
defaults.synchronize()
print("saved \(todos.count)")
} else if let storedTodoData = defaults.dataForKey("TDDATA"),
storedTodos = NSKeyedUnarchiver.unarchiveObjectWithData(storedTodoData) as? [TodoModel] {
// There was stored data! Use it!
todos = storedTodos
print("Used \(todos.count) stored todos")
}
}
In addition, we must implement the NSCoding protocol in your model. This should be something like this:
class TodoModel: NSObject, NSCoding {
var myInt: Int = 0
var myString: String?
var myArray: [String]?
required init?(coder aDecoder: NSCoder) {
myInt = aDecoder.decodeIntegerForKey("myInt")
myString = aDecoder.decodeObjectForKey("myString") as? String
myArray = aDecoder.decodeObjectForKey("myArray") as? [String]
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(myInt, forKey: "myInt")
aCoder.encodeObject(myString, forKey: "myString")
aCoder.encodeObject(myArray, forKey: "myArray")
}
}
(Of course, replace myInt, myString, myArray, etc, with whatever properties your model might have.)

Related

fatal error: unexpectedly found nil while unwrapping an Optional value in Swift 3

this Struct is work in swift 2
I have a Swift 3 struct like this.
let tempContacts = NSMutableArray()
let arrayOfArray = NSMutableArray()
I have encode The Person Object in this for loop
for person in tempContacts as! [Person] {
let encodedObject: Data = NSKeyedArchiver.archivedData(withRootObject: person) as Data
arrayOfArray.add(encodedObject)
}
I have decode the data in this for loop
let tempContacts2 = NSMutableArray()
for data in arrayOfArray {
let person: Person = NSKeyedUnarchiver.unarchiveObject(with: data as! Data) as! Person
tempContacts2.add(person)
}
but unarchiveObject is always return nil value
First your model class should conform to the NSCoder protocol. The rest is really simple, there's no need to store the archived results for each object in an array, you can pass the initial array directly to NSKeyedArchiver like this :
class Person: NSObject, NSCoding {
var name = ""
init(name: String) {
self.name = name
}
// NSCoder
required convenience init?(coder decoder: NSCoder) {
guard let name = decoder.decodeObject(forKey: "name") as? String else { return nil }
self.init(name: name)
}
func encode(with coder: NSCoder) {
coder.encode(self.name, forKey: "name")
}
}
let tempContacts = [Person(name: "John"), Person(name: "Mary")]
let encodedObjects = NSKeyedArchiver.archivedData(withRootObject: tempContacts)
let decodedObjects = NSKeyedUnarchiver.unarchiveObject(with: encodedObjects)
As a side note : if NSCoder compliance is correctly implemented in your model class, you can of course use your way of archiving/unarchiving individual objects too. So your original code works too, with some minor adjustments:
for person in tempContacts {
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: person)
arrayOfArray.add(encodedObject)
}
var tempContacts2 = [Person]()
for data in arrayOfArray {
let person: Person = NSKeyedUnarchiver.unarchiveObject(with: data as! Data) as! Person
tempContacts2.append(person)
}
Note 2: if you absolutely wants to use NSMutableArrays that's possible too, just define tempContacts like this:
let tempContacts = NSMutableArray(array: [Person(name: "John"), Person(name: "Mary")])
The rest is working without changes.
Note 3: The reason it used to work in Swift 2 and it's not working anymore in Swift 3 is that the signature for the NSCoder method func encode(with coder:) changed in Swift 3.

use NSUserDefaults with array of struct type

I have problems to figure out how to save my string of type "RiskEntry" with NSUserDefaults. I already went through some other posts, but somehow I still did not manage to solve this particular issue.
Let me explain what the code from below does right now: I get some data from my class CustomCell in the following code snippet. Here I first check with an "identifier" which array to update with the new array value "consequences".
It all works fine and the updated array is stored in riskEntry.
However, I cannot work out how to store this with NSUserDefaults now. When I try it with e.g. riskItemDefaults.set(riskEntry, forKey: "riskItem") I get an exception error.
Any idea what I am doing wrong here?
SWIFT3 (I removed all code not relevant for this question)
class: RiskPlan: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellUpdaterDelegate {
var riskEntry = [RiskEntry]()
var riskItemDefaults = UserDefaults.standard
// ------ start of delegate function (receiving from CustomCell) ---------
func transferData(consequencesTranferred: String, identifier: String) {
if let index = riskEntry.index(where: {$0.title as String == identifier}) {
riskEntry[index].consequences = consequencesTranferred
} else {
print ("nothing")
}
// save with NSUserDefaults
riskItemDefaults.set(riskEntry, forKey: "riskItem")
}
}
This is my struct:
public struct RiskEntry {
let title: String
var consequences: String
}
my Custom Cell
// ---------------- delegate to transfer entered data to VC -----------------
protocol CustomCellUpdaterDelegate {
func transferData(consequencesTranferred: String, identifier: String)
}
// ---------------- start of class CellCustomized -----------------
class CustomCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate, UITextViewDelegate {
var delegate: CustomCellUpdaterDelegate?
// text fields, text views and picker views
#IBOutlet weak var riskTitle: UITextView!
#IBOutlet weak var consequences: UITextView!
// ---------------- listener for text view to save input in string when editing is finished -----------------
func textViewDidEndEditing(_ textView: UITextView) {
if textView.tag == 1 {
textConsequences = consequences.text
nameIdentifier = riskTitle.text
delegate?.transferData(consequencesTranferred: self.textConsequences, identifier: nameIdentifier)
} else {
print ("nothing")
}
}
}
The problem is you can't save your custom array in NSUserDefaults. To do that you should change them to NSData then save it in NSUserDefaults
Here is the code I used in my project it's in swift 2 syntax and I don't think it's going be hard to convert it to swift 3
let data = NSKeyedArchiver.archivedDataWithRootObject(yourObject);
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "yourKey")
NSUserDefaults.standardUserDefaults().synchronize()
and to the get part use this combination
if let data = NSUserDefaults.standardUserDefaults().objectForKey("yourKey") as? NSData {
let myItem = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? yourType
}
hope this will help
Saving objects in UserDefaults have very specific restrictions:
set(_:forKey:) reference:
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
You need to serialize your model, either using NSCoding or as an alternative using JSON, to map to a supported value by UserDefaults.
The closest type to a Swift struct that UserDefaults supports might be an NSDictionary. You could copy the struct elements into an Objective C NSDictionary object before saving the data.
I was able to program a solution based on #ahruss (How to save an array of custom struct to NSUserDefault with swift?). However, I modified it for swift 3 and it also shows how to implement this solution in a UITableView. I hope it can help someone in the future:
Add the extension from below to your structure (adjust it to your own variables)
Save the required array item like this:
let encoded = riskEntry.map { $0.encode() }
riskItemDefaults.set(encoded, forKey: "consequences")
riskItemDefaults.synchronize()
Load your item like this
let dataArray = riskItemDefaults.object(forKey: "consequences") as! [NSData]
let savedFoo = dataArray.map { RiskEntry(data: $0)! }
If you'd like to show the saved array item in your cells, proceed this way:
cell.consequences.text = savedFoo[indexPath.row].consequences as String
Here is the complete code, modified for Swift3
structure
// ---------------- structure for table row content -----------------
struct RiskEntry {
let title: String
var consequences: String
}
extension
extension RiskEntry {
init?(data: NSData) {
if let coding = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? Encoding {
title = coding.title as String
consequences = (coding.consequences as String?)!
} else {
return nil
}
}
func encode() -> NSData {
return NSKeyedArchiver.archivedData(withRootObject: Encoding(self)) as NSData
}
private class Encoding: NSObject, NSCoding {
let title : NSString
let consequences : NSString?
init(_ RiskEntry: RiskEntry) {
title = RiskEntry.title as NSString
consequences = RiskEntry.consequences as NSString?
}
public required init?(coder aDecoder: NSCoder) {
if let title = aDecoder.decodeObject(forKey: "title") as? NSString {
self.title = title
} else {
return nil
}
consequences = aDecoder.decodeObject(forKey: "consequences") as? NSString
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(consequences, forKey: "consequences")
}
}
}

iOS: Custom object's int variable returns nil in NSUserDefaults

I have this weird issue. I'm saving custom class object in NSUserDefaults, and while retrieving the data I get nil for int variable of the object. Below is the custom class
class User {
var name: String?
var user_id: Int?
var account_id: Int?
var location: String?
}
I'm saving the object as,
let defaults = NSUserDefaults.standardUserDefaults()
var data = NSKeyedArchiver.archivedDataWithRootObject([user]) // I can see the int values for the user objects here
defaults.setObject(data, forKey: "all_users")
Retrieving the data as,
let defaults = NSUserDefaults.standardUserDefaults()
let data = defaults.dataForKey("all_users")
var users = [Users]()
if data != nil {
let userData = NSKeyedUnarchiver.unarchiveObjectWithData(data!) as! [Users]
for usr in userData {
print("\(usr.name!)") // Prints the name
print("\(usr.user_id!)") // Nil value here
users.append(usr)
}
}
I have absolutely no idea about the reason for this behavior.
Custom classes that have none property list items need to conform to NSCoding to be able to be saved in NSUserDefaults.
Here is a guide to conforming to NSCoding: http://nshipster.com/nscoding/
You will need both of these functions:
init(coder decoder: NSCoder) {
self.name = decoder.decodeObjectForKey("name") as String
self.user_id = decoder.decodeIntegerForKey("user_id")
self.account_id = decoder.decodeIntegerForKey("account_id")
self.location = decoder.decodeObjectForKey("self.location") as String
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(self.user_id, forKey: "user_id")
coder.encodeInt(account_id, forKey: "account_id")
coder.encodeObject(self.location, forKey: "location")
}

NSKeyedUnarchiver won't return data

I am making an app that tracks a user's workouts. I have two custom classes, the first being ExerciseModel, which holds the data for each exercise performed during the workout, including the name, sets, reps, etc. Here is my data model:
import UIKit
class ExerciseModel: NSObject, NSCoding
{
// MARK: Properties
var name: String
var sets: Int
var reps: Int
var heartrate: Int?
var type: String?
//MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("exercises")
// MARK: Initialization
init?(name: String, sets: Int, reps: Int, heartrate: Int?, type: String)
{
// MARK: Initlaize stored properties
self.name = name
self.sets = sets
self.reps = reps
self.heartrate = heartrate
self.type = type
super.init()
// Initialization should fail if there is no name or sets is negative
if name.isEmpty || sets < 0
{
return nil
}
}
struct PropertyKey
{
static let nameKey = "name"
static let setKey = "sets"
static let repKey = "reps"
static let heartrateKey = "heartrate"
static let typekey = "type"
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder)
{
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeInteger(sets, forKey: PropertyKey.setKey)
aCoder.encodeInteger(reps, forKey: PropertyKey.repKey)
aCoder.encodeObject(type, forKey: PropertyKey.typekey)
}
required convenience init?(coder aDecoder: NSCoder)
{
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let sets = aDecoder.decodeIntegerForKey(PropertyKey.setKey)
let reps = aDecoder.decodeIntegerForKey(PropertyKey.repKey)
let heartrate = aDecoder.decodeIntegerForKey(PropertyKey.heartrateKey)
let type = aDecoder.decodeObjectForKey(PropertyKey.typekey) as? String
// Must call designated initializer
self.init(name: name, sets: sets, reps: reps, heartrate: heartrate, type: type!)
}
init?(name: String, sets: Int, reps: Int, heartrate: Int, type: String)
{
// Initialize stored properties.
self.name = name
self.sets = sets
self.reps = reps
self.heartrate = heartrate
self.type = type
}
}
My second custom class is called WorkoutStorage, and this is meant to allow the user to save entire workouts and retrieve them later. The exercise property is an array of ExerciseModel objects, described above. Here is my data model for WorkoutStorage:
//
import UIKit
#objc(WorkoutStorage)
class WorkoutStorage: NSObject, NSCoding
{
// MARK: Properties
var name: String
var date: NSDate
var exercises: [ExerciseModel]
var maxHR: Int
var avgHR: Int
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("storedWorkouts")
// MARK: Initialization
init?(name: String, date: NSDate, exercises: [ExerciseModel], maxHR: Int, avgHR: Int)
{
//MARK: Initialize Stored Properties
self.name = name
self.date = date
self.exercises = exercises
self.maxHR = maxHR
self.avgHR = avgHR
super.init()
}
struct PropertyKey
{
static let nameKey = "name"
static let dateKey = "date"
static let exercisesKey = "exercises"
static let maxHRKey = "maxHR"
static let avgHRKey = "avgHR"
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder)
{
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(date, forKey: PropertyKey.dateKey)
aCoder.encodeObject(exercises, forKey: PropertyKey.exercisesKey)
aCoder.encodeInteger(maxHR, forKey: PropertyKey.maxHRKey)
aCoder.encodeInteger(avgHR, forKey: PropertyKey.avgHRKey)
}
required convenience init?(coder aDecoder: NSCoder)
{
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let date = aDecoder.decodeObjectForKey(PropertyKey.dateKey) as! NSDate
let exercises = aDecoder.decodeObjectForKey(PropertyKey.exercisesKey) as! [ExerciseModel]
let maxHR = aDecoder.decodeIntegerForKey(PropertyKey.maxHRKey)
let avgHR = aDecoder.decodeIntegerForKey(PropertyKey.avgHRKey)
// Must call designated initializer
self.init(name: name, date: date, exercises: exercises, maxHR: maxHR, avgHR: avgHR)
}
}
I followed the Apple tutorial for Persist Data to set up NSKeyedArchiver and NSKeyedUnarchiver for this, but I am still having trouble retrieving my data. When I try to load the Workouts, I call the following function:
func loadStoredWorkouts() -> WorkoutStorage
{
NSKeyedUnarchiver.setClass(WorkoutStorage.self, forClassName: "WorkoutStorage")
NSKeyedArchiver.setClassName("WorkoutStorage", forClass: WorkoutStorage.self)
print("\(WorkoutStorage.ArchiveURL.path!)")
return NSKeyedUnarchiver.unarchiveObjectWithFile(WorkoutStorage.ArchiveURL.path!) as! WorkoutStorage
}
Currently I can only return a single WorkoutStorage object, but when I attempt to retrieve an array containing all the stored WorkoutStorage objects, I get an error saying: Could not cast value of type 'Workout_Tracker.WorkoutStorage' (0x1000fcc80) to 'NSArray' (0x19f6b2418). I have read a lot of documentation trying to figure out why this will only return a single object, as well as checked out questions with similar issues, but to no avail. I originally set up my app following the Apple Persist Data tutorial to store and load my ExerciseModel objects, and that seems to work flawlessly. I set up the WorkoutStorage class the same way, but there seems to be an issue here.
Any help would be greatly appreciated!!
**Edit*
Here is the code I use to archive the WorkoutStorage object:
func saveWorkoutStorageObject(currentWorkout: WorkoutStorage)
{
NSKeyedUnarchiver.setClass(WorkoutStorage.self, forClassName: "WorkoutStorage")
NSKeyedArchiver.setClassName("WorkoutStorage", forClass: WorkoutStorage.self)
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(currentWorkout, toFile: WorkoutStorage.ArchiveURL.path!)
if !isSuccessfulSave
{
print("Failed to save exercises")
}
if isSuccessfulSave
{
print("Successful save of current workout: \(currentWorkout)")
}
}
Workouts are only created one at a time by the user, so each time one is completed, I pass the object to the above function to archive it.
To unarchive all the objects, I was trying to do something along the lines of:
var workouts = [WorkoutStorage]()
override func viewDidLoad()
{
super.viewDidLoad()
workouts = loadStoredWorkouts()
}
where the loadStoredWorkouts() function would be:
func loadStoredWorkouts() -> [WorkoutStorage]
{
NSKeyedUnarchiver.setClass(WorkoutStorage.self, forClassName: "WorkoutStorage")
NSKeyedArchiver.setClassName("WorkoutStorage", forClass: WorkoutStorage.self)
print("\(WorkoutStorage.ArchiveURL.path!)")
return NSKeyedUnarchiver.unarchiveObjectWithFile(WorkoutStorage.ArchiveURL.path!) as! [WorkoutStorage]
}
Your saveWorkoutStorageObject only archives a single workout. It doesn't archive the array, so of course you can't unarchive an array.
You need to archive the workouts array if you want to be able to unarchive an array.
Each time you archive something to a file you replace the contents of the file. It doesn't append to the end.
Since NSKeyedArchiver.archiveRootObject automatically archives child objects, all you need to do is archive the array and your WorkoutStorage objects will be archived automagically
func saveWorkouts(workouts:[WorkoutStorage])
{
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(workouts, toFile: WorkoutStorage.ArchiveURL.path!)
if isSuccessfulSave
{
print("Successful save of workouts: \(workouts)")
} else {
print("Failed to save exercises")
}
}

Converting custom class object into NSData

I have a custom class that I want to save into NSUserDefaults. I am told that I need to convert the class object into data in order to save it to NSUserDefaults. I found a lot of discrete string or ints to NSData examples but nothing on custom class to NSData. I know very little about the intricacies of NSData encoding etc. Any help is appreciated
EDIT: While I understand there are similar answers here, none of them are in Swift. Translating between the languages is doable, but it is extremely tedious and sometimes very counter-intuitive.
Here is one simple example for you:
//Custom class.
class Person: NSObject, NSCoding {
var name: String!
var age: Int!
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String
self.age = decoder.decodeObjectForKey("age") as! Int
}
convenience init(name: String, age: Int) {
self.init()
self.name = name
self.age = age
}
func encodeWithCoder(coder: NSCoder) {
if let name = name { coder.encodeObject(name, forKey: "name") }
if let age = age { coder.encodeObject(age, forKey: "age") }
}
}
//create an instance of your custom class.
var newPerson = [Person]()
//add some values into custom class.
newPerson.append(Person(name: "Leo", age: 45))
newPerson.append(Person(name: "Dharmesh", age: 25))
//store you class object into NSUserDefaults.
let personData = NSKeyedArchiver.archivedDataWithRootObject(newPerson)
NSUserDefaults().setObject(personData, forKey: "personData")
//get your object from NSUserDefaults.
if let loadedData = NSUserDefaults().dataForKey("personData") {
if let loadedPerson = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? [Person] {
loadedPerson[0].name //"Leo"
loadedPerson[0].age //45
}
}
Tested with playground.
Hope this helps.
This following sample code is based on Richie Rich's answer (see above) and passes tests in this environment:
Xcode version 9.1 (9B55)
Swift version 4.0.2 (swiftlang-900.0.69.2 clang-900.0.38, Target: x86_64-apple-macosx10.9)
MacBook Air (11-inch, Mid 2012) with macOS High Sierra (version 10.13.1)
// Foundation is required to NSObject and NSCoding
import Foundation
// A custom class called Person with two properties (a string name and an
// integer age), that is a subclass of NSObject and adopts NSCoding protocol.
class Person: NSObject, NSCoding {
var name: String!
var age: Int!
// The convenience initializer for class Person
// Reference
// https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID217
convenience init(name: String, age: Int) {
// self.init() is the designated initializer for class Person.
// Reference
// https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID219
self.init()
self.name = name
self.age = age
}
// The initializer init(coder:) is required by NSCoding protocol
// Reference
// https://developer.apple.com/documentation/foundation/nscoding
// https://developer.apple.com/documentation/foundation/nscoding/1416145-init
required convenience init(coder aDecoder: NSCoder) {
self.init()
// as! is a type casting operator
// Reference
// https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID388
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.age = aDecoder.decodeInteger(forKey: "age")
}
// The instance method encode(with:) is required by NSCoding protocol
// Reference
// https://developer.apple.com/documentation/foundation/nscoding
// https://developer.apple.com/documentation/foundation/nscoding/1413933-encode
func encode(with anEncoder: NSCoder) {
if let name = name {
anEncoder.encode(name, forKey: "name")
}
if let age = age {
anEncoder.encode(age, forKey: "age")
}
}
}
// Create an array (or, generally speaking, a collection) as a container to
// hold instances of our custom class type Person.
// Reference
// https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html
var anArrayOfPersons = [Person]()
print(anArrayOfPersons.count) // 0
// Add two instances into anArrayOfPersons.
// Reference
// https://developer.apple.com/documentation/swift/array
// https://developer.apple.com/documentation/swift/array/1538872-append
anArrayOfPersons.append(Person(name: "Cong", age: 33))
anArrayOfPersons.append(Person(name: "Sunny", age: 2))
// Archive anArrayOfPersons into NSData using NSKeyedArchiver.
// Reference
// https://developer.apple.com/documentation/foundation/nskeyedarchiver
// https://developer.apple.com/documentation/foundation/nskeyedarchiver/1413189-archiveddata
let dataToSave = NSKeyedArchiver.archivedData(withRootObject: anArrayOfPersons)
// Persist data. Storing anArrayOfPersons into UserDefaults as data.
// Reference
// https://developer.apple.com/documentation/foundation/userdefaults
// https://developer.apple.com/documentation/foundation/userdefaults/1414067-set
UserDefaults().set(dataToSave, forKey: "tagOfData")
// Take our stored data (in previous step) from UserDefaults using the key
// "personData". Optional binding is used to make sure the retrieved data is
// not nil.
// Reference
// https://developer.apple.com/documentation/foundation/userdefaults
// https://developer.apple.com/documentation/foundation/userdefaults/1409590-data
// https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID333
if let dataRetrieved = UserDefaults().data(forKey: "tagOfData"),
// Decode our instance objects from the retrieved data
// Reference
// https://developer.apple.com/documentation/foundation/nskeyedunarchiver
// https://developer.apple.com/documentation/foundation/nskeyedunarchiver/1413894-unarchiveobject
let anArrayOfPersonsRetrieved = NSKeyedUnarchiver.unarchiveObject(with: dataRetrieved) as? [Person] {
// See how many bytes the data we retrieved has.
print(dataRetrieved) // 393 bytes
// See if the name and age properties are the same as what we stored.
print(anArrayOfPersonsRetrieved[0].name) // "Cong"
print(anArrayOfPersonsRetrieved[0].age) // 45
print(anArrayOfPersonsRetrieved[1].name) // "Sunny"
print(anArrayOfPersonsRetrieved[1].age) // 2
}
This link can help you
It is important your class extend NSObject and NSCoding, because the convert need be its class, NSCoding is an interface to serialize and deserialize your class
Saving custom SWIFT class with NSCoding to UserDefaults

Resources