Swift 3 saving and retrieving custom object from userDefaults

左心房为你撑大大i 提交于 2019-11-26 03:29:47

问题


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?


回答1:


Swift 4 Note

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


Swift 3

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
        let newPerson = Person(name: "Joe", age: 10)
        var people = [Person]()
        people.append(newPerson)
        let encodedData = NSKeyedArchiver.archivedData(withRootObject: people)
        UserDefaults.standard.set(encodedData, forKey: "people")

        // retrieving a value for a key
        if let data = UserDefaults.standard.data(forKey: "people"),
            let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Person] {
            myPeopleList.forEach({print( $0.name, $0.age)})  // Joe 10
        } else {
            print("There is an issue")
        }
    }
}



回答2:


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")



回答3:


Try this:

self.age = aDecoder.decodeObject(forKey: "age") as? Int ?? aDecoder.decodeInteger(forKey: "age")



回答4:


In Swift 4:

You can use Codable to save and retrieve custom object from the Userdefaults. If you're doing it frequently then you can add as extension and use it like below.

extension UserDefaults {

   func save<T:Encodable>(customObject object: T, inKey key: String) {
       let encoder = JSONEncoder()
       if let encoded = try? encoder.encode(object) {
           self.set(encoded, forKey: key)
       }
   }

   func retrieve<T:Decodable>(object type:T.Type, fromKey key: String) -> T? {
       if let data = self.data(forKey: key) {
           let decoder = JSONDecoder()
           if let object = try? decoder.decode(type, from: data) {
               return object
           }else {
               print("Couldnt decode object")
               return nil
           }
       }else {
           print("Couldnt find key")
           return nil
       }
   }

}

Your Class must follow Codable. Its just a typealias for both Encodable & Decodable Protocol.

class UpdateProfile: Codable {
  //Your stuffs
}

Usage:

let updateProfile = UpdateProfile()

//To save the object
UserDefaults.standard.save(customObject: updateProfile, inKey: "YourKey")

//To retrieve the saved object
let obj = UserDefaults.standard.retrieve(object: UpdateProfile.self, fromKey: "YourKey")

For more Encoding and Decoding Custom types, Please go through the Apple's documentation.




回答5:


A simple example of saving "Custom Object" in UserDefaults would be as below :

You do not need to write the boilerplate code for saving/retrieving objects with the help of THE GREAT 'CODABLE', that's what it is there for you to rescue from irritating manual encoding/decoding

So just get rid of below two methods from your code if you're already using NSCoding and switch to Codable (Combination of Encodable + Decodable) protocol

required init(coder decoder: NSCoder) // NOT REQUIRED ANY MORE, DECODABLE TAKES CARE OF IT

func encode(with coder: NSCoder) // NOT REQUIRED ANY MORE, ENCODABLE TAKES CARE OF IT

Let's get started with the simplicity of Codable...

Create a custom class or struct you want to store in UserDefaults

struct Person : Codable {
    var name:String
}

OR

class Person : Codable {

    var name:String

    init(name:String) {
        self.name = name
    }
}

Save object in UserDefaults as below

 if let encoded = try? JSONEncoder().encode(Person(name: "Dhaval")) {
     UserDefaults.standard.set(encoded, forKey: "kSavedPerson")
 }

Load object from UserDefaults as below

guard let savedPersonData = UserDefaults.standard.object(forKey: "kSavedPerson") as? Data else { return }
guard let savedPerson = try? JSONDecoder().decode(Person.self, from: savedPersonData) else { return }

print("\n Saved person name : \(savedPerson.name)")

That's it ...

Happy saving / loading .. :)



来源:https://stackoverflow.com/questions/37980432/swift-3-saving-and-retrieving-custom-object-from-userdefaults

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!