如何用swift将对象数组保存到NSUserDefault ?

时间:2021-02-04 13:21:45

this is a class Place I defined:

这是我定义的一个类空间:

class Place: NSObject {

    var latitude: Double
    var longitude: Double

    init(lat: Double, lng: Double, name: String){
        self.latitude = lat
        self.longitude = lng
    }

    required init(coder aDecoder: NSCoder) {
        self.latitude = aDecoder.decodeDoubleForKey("latitude")
        self.longitude = aDecoder.decodeDoubleForKey("longitude")
    }

    func encodeWithCoder(aCoder: NSCoder!) {
        aCoder.encodeObject(latitude, forKey: "latitude")
        aCoder.encodeObject(longitude, forKey: "longitude")
    }

}

This is how I tried to save an array of Places:

这就是我尝试保存一系列位置的方式:

var placesArray = [Place]

//...

func savePlaces() {
    NSUserDefaults.standardUserDefaults().setObject(placesArray, forKey: "places")
    println("place saved")
}

It didn't work, this is what I get on the console:

它不起作用,这是我在控制台所得到的:

Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')

I am new to iOS, could you help me ?

我是iOS新手,你能帮我吗?

SECOND EDITION

第二版

I found a solution to save the data :

我找到了一种保存数据的方法:

func savePlaces(){
    let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
   NSUserDefaults.standardUserDefaults().setObject(myData, forKey: "places")
    println("place saved")
}

But I get an error when loading the data with this code :

但是当我用这个代码加载数据时,我得到了一个错误:

 let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData

 if placesData != nil {
      placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData!) as [Place]
 }

the error is :

错误的是:

[NSKeyedUnarchiver decodeDoubleForKey:]: value for key (latitude) is not a double number'

I am pretty sure I archived a Double, there is an issue with the saving/loading process

我很确定我归档了一个Double,保存/加载过程有问题

Any clue ?

有线索吗?

5 个解决方案

#1


22  

From the Property List Programming Guide:

从属性列表编程指南:

If a property-list object is a container (that is, an array or dictionary), all objects contained within it must also be property-list objects. If an array or dictionary contains objects that are not property-list objects, then you cannot save and restore the hierarchy of data using the various property-list methods and functions.

如果一个属性列表对象是一个容器(即一个数组或字典),那么其中包含的所有对象都必须是属性列表对象。如果数组或字典包含非属性列表对象的对象,则不能使用各种属性列表方法和函数保存和恢复数据的层次结构。

You'll need to convert the object to and from an NSData instance using NSKeyedArchiver and NSKeyedUnarchiver.

您需要使用NSKeyedArchiver和NSKeyedUnarchiver将对象转换为NSData实例。

For example:

例如:

func savePlaces(){
    let placesArray = [Place(lat: 123, lng: 123, name: "hi")]
    let placesData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
    NSUserDefaults.standardUserDefaults().setObject(placesData, forKey: "places")
}

func loadPlaces(){
    let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData

    if let placesData = placesData {
        let placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData) as? [Place]

        if let placesArray = placesArray {
            // do something…
        }

    }
}

#2


16  

Swift 3 & 4

The following is the complete example code in Swift 3 & 4.

下面是Swift 3和4中的完整示例代码。

import Foundation

class Place: NSObject, NSCoding {

    var latitude: Double
    var longitude: Double
    var name: String

    init(latitude: Double, longitude: Double, name: String) {
        self.latitude = latitude
        self.longitude = longitude
        self.name = name
    }

    required init?(coder aDecoder: NSCoder) {
        self.latitude = aDecoder.decodeDouble(forKey: "latitude")
        self.longitude = aDecoder.decodeDouble(forKey: "longitude")
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(latitude, forKey: "latitude")
        aCoder.encode(longitude, forKey: "longitude")
        aCoder.encode(name, forKey: "name")
    }
}

func savePlaces() {
    var placesArray: [Place] = []
    placesArray.append(Place(latitude: 12, longitude: 21, name: "place 1"))
    placesArray.append(Place(latitude: 23, longitude: 32, name: "place 2"))
    placesArray.append(Place(latitude: 34, longitude: 43, name: "place 3"))

    let placesData = NSKeyedArchiver.archivedData(withRootObject: placesArray)
    UserDefaults.standard.set(placesData, forKey: "places")
}

func loadPlaces() {
    guard let placesData = UserDefaults.standard.object(forKey: "places") as? NSData else {
        print("'places' not found in UserDefaults")
        return
    }

    guard let placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as? [Place] else {
        print("Could not unarchive from placesData")
        return
    }

    for place in placesArray {
        print("")
        print("place.latitude: \(place.latitude)")
        print("place.longitude: \(place.longitude)")
        print("place.name: \(place.name)")
    }
}

 

 

Example Use:

savePlaces()
loadPlaces()

 

 

Console Output:

place.latitude: 12.0
place.longitude: 21.0
place.name: 'place 1'

place.latitude: 23.0
place.longitude: 32.0
place.name: 'place 2'

place.latitude: 34.0
place.longitude: 43.0
place.name: 'place 3'

#3


6  

You have prepared Place for archiving, but you are now assuming that an array of Place will be archived automatically as you store it in NSUserDefaults. It won't be. You have to archive it. The error message is telling you this. The only things that can be saved in NSUserDefaults are objects with property list types: NSArray, NSDictionary, NSString, NSData, NSDate and NSNumber. A Place object is not one of those.

您已经为存档准备好了Place,但是现在假设在NSUserDefaults中存储位置数组时将自动存档。它不会。你得把它存档。错误消息告诉您这一点。在NSUserDefaults中唯一可以保存的是属性列表类型的对象:NSArray、NSDictionary、NSString、NSData、NSDate和NSNumber。地点对象不是其中之一。

Instead of trying to save the array directly, archive it. Now it is an NSData — and that is one of the property list object types:

与其尝试直接保存数组,不如将其存档。现在它是一个NSData -这是属性列表对象类型之一:

let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)

Now you can store myData in NSUserDefaults, because it is an NSData. Of course when you pull it out again you will also have to unarchive it to turn it from an NSData back into an array of Place.

现在可以在NSUserDefaults中存储myData,因为它是NSData。当然,当你再次取出它的时候,你也需要将它从NSData转换回一个位置数组。

EDIT: By the way, it occurs to me, as an afterthought, that your Place class may have to explicitly adopt the NSCoding protocol to get this to work. You seem to have omitted that step.

编辑:顺便说一下,我后来想到,您的Place类可能必须显式地采用NSCoding协议才能使其工作。你似乎漏掉了那一步。

#4


5  

Swift 4

斯威夫特4

We need to serialize our swift object to save it into userDefaults.

我们需要序列化我们的swift对象,将其保存到userDefaults中。

In swift 4 we can use Codable protocol, which makes our life easy on serialization and JSON parsing

在swift 4中,我们可以使用可编码协议,这使我们在序列化和JSON解析方面更容易

Workflow(Save swift object in UserDefaults):

工作流(用户默认保存swift对象):

  1. Confirm Codable protocol to model class(class Place : Codable).
  2. 确认可编码协议到模型类(类位置:可编码)。
  3. Create object of class.
  4. 创建类的对象。
  5. Serialize that class using JsonEncoder class.
  6. 使用JsonEncoder类序列化该类。
  7. Save serialized(Data) object to UserDefaults.
  8. 将序列化(数据)对象保存为UserDefaults。

Workflow(Get swift object from UserDefaults):

工作流程(从用户默认设置中获取swift对象):

  1. Get data from UserDefaults(Which will return Serialized(Data) object)
  2. 从UserDefaults中获取数据(它会返回序列化的数据)
  3. Decode Data using JsonDecoder class
  4. 使用JsonDecoder类解码数据

Swift 4 Code:

斯威夫特4代码:

class Place: Codable {
    var latitude: Double
    var longitude: Double

    init(lat : Double, long: Double) {
        self.latitude = lat
        self.longitude = long
    }

    public static func savePlaces(){
        var placeArray = [Place]()
        let place1 = Place(lat: 10.0, long: 12.0)
        let place2 = Place(lat: 5.0, long: 6.7)
        let place3 = Place(lat: 4.3, long: 6.7)
        placeArray.append(place1)
        placeArray.append(place2)
        placeArray.append(place3)
        let placesData = try! JSONEncoder().encode(placeArray)
        UserDefaults.standard.set(placesData, forKey: "places")
    }

    public static func getPlaces() -> [Place]?{
        let placeData = UserDefaults.standard.data(forKey: "places")
        let placeArray = try! JSONDecoder().decode([Place].self, from: placeData!)
        return placeArray
    }
}

#5


1  

In Swift 4.0+ we can use the type alias Codable which consist of 2 protocols: Decodable & Encodable.

在Swift 4.0+中,我们可以使用包含2个协议的类型别名:可解码的和可编码的。

For convenience, I've created a generic decode and encode methods that are type constrained to Codable:

为了方便起见,我创建了一个通用的解码和编码方法,它们的类型被限制为可编码:

extension UserDefaults {
    func decode<T : Codable>(for type : T.Type, using key : String) -> T? {
        let defaults = UserDefaults.standard
        guard let data = defaults.object(forKey: key) as? Data else {return nil}
        let decodedObject = try? PropertyListDecoder().decode(type, from: data)
        return decodedObject
    }

    func encode<T : Codable>(for type : T, using key : String) {
        let defaults = UserDefaults.standard
        let encodedData = try? PropertyListEncoder().encode(type)
        defaults.set(encodedData, forKey: key)
        defaults.synchronize()
    }
}

Usage - saving an object/array/dictionary:

用法-保存对象/数组/字典:

Let's say we have a custom object:

假设我们有一个自定义对象:

struct MyObject: Codable {
    let counter: Int
    let message: String
}

and we have created an instance from it:

我们从中创建了一个实例:

let myObjectInstance = MyObject(counter: 10, message: "hi there")

Using the the generic extension above we can now save this object as follow:

使用上面的通用扩展,我们现在可以保存这个对象如下:

UserDefaults.standard.encode(for: myObjectInstance, using: String(describing: MyObject))

Saving an array of the same type:

保存相同类型的数组:

UserDefaults.standard.encode(for:[myFirstObjectInstance, mySecondObjectInstance], using: String(describing: MyObject))

Saving a dictionary with that type:

保存具有该类型的字典:

let dictionary = ["HashMe" : myObjectInstance]
UserDefaults.standard.encode(for: dictionary, using: String(describing: MyObject))

Usage - loading an object/array/dictionary:

用法-加载对象/数组/字典:

Loading a single object:

加载一个对象:

let myDecodedObject = UserDefaults.standard.decode(for: MyObject.self, using: String(describing: MyObject))

Loading an array of the same type:

加载相同类型的数组:

let myDecodedObject = UserDefaults.standard.decode(for: [MyObject].self, using: String(describing: MyObject))

Loading a dictionary with that type:

在字典中装入这种类型的词典:

let myDecodedObject = UserDefaults.standard.decode(for: ["HashMe" : myObjectInstance].self, using: String(describing: MyObject))

#1


22  

From the Property List Programming Guide:

从属性列表编程指南:

If a property-list object is a container (that is, an array or dictionary), all objects contained within it must also be property-list objects. If an array or dictionary contains objects that are not property-list objects, then you cannot save and restore the hierarchy of data using the various property-list methods and functions.

如果一个属性列表对象是一个容器(即一个数组或字典),那么其中包含的所有对象都必须是属性列表对象。如果数组或字典包含非属性列表对象的对象,则不能使用各种属性列表方法和函数保存和恢复数据的层次结构。

You'll need to convert the object to and from an NSData instance using NSKeyedArchiver and NSKeyedUnarchiver.

您需要使用NSKeyedArchiver和NSKeyedUnarchiver将对象转换为NSData实例。

For example:

例如:

func savePlaces(){
    let placesArray = [Place(lat: 123, lng: 123, name: "hi")]
    let placesData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
    NSUserDefaults.standardUserDefaults().setObject(placesData, forKey: "places")
}

func loadPlaces(){
    let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData

    if let placesData = placesData {
        let placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData) as? [Place]

        if let placesArray = placesArray {
            // do something…
        }

    }
}

#2


16  

Swift 3 & 4

The following is the complete example code in Swift 3 & 4.

下面是Swift 3和4中的完整示例代码。

import Foundation

class Place: NSObject, NSCoding {

    var latitude: Double
    var longitude: Double
    var name: String

    init(latitude: Double, longitude: Double, name: String) {
        self.latitude = latitude
        self.longitude = longitude
        self.name = name
    }

    required init?(coder aDecoder: NSCoder) {
        self.latitude = aDecoder.decodeDouble(forKey: "latitude")
        self.longitude = aDecoder.decodeDouble(forKey: "longitude")
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(latitude, forKey: "latitude")
        aCoder.encode(longitude, forKey: "longitude")
        aCoder.encode(name, forKey: "name")
    }
}

func savePlaces() {
    var placesArray: [Place] = []
    placesArray.append(Place(latitude: 12, longitude: 21, name: "place 1"))
    placesArray.append(Place(latitude: 23, longitude: 32, name: "place 2"))
    placesArray.append(Place(latitude: 34, longitude: 43, name: "place 3"))

    let placesData = NSKeyedArchiver.archivedData(withRootObject: placesArray)
    UserDefaults.standard.set(placesData, forKey: "places")
}

func loadPlaces() {
    guard let placesData = UserDefaults.standard.object(forKey: "places") as? NSData else {
        print("'places' not found in UserDefaults")
        return
    }

    guard let placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as? [Place] else {
        print("Could not unarchive from placesData")
        return
    }

    for place in placesArray {
        print("")
        print("place.latitude: \(place.latitude)")
        print("place.longitude: \(place.longitude)")
        print("place.name: \(place.name)")
    }
}

 

 

Example Use:

savePlaces()
loadPlaces()

 

 

Console Output:

place.latitude: 12.0
place.longitude: 21.0
place.name: 'place 1'

place.latitude: 23.0
place.longitude: 32.0
place.name: 'place 2'

place.latitude: 34.0
place.longitude: 43.0
place.name: 'place 3'

#3


6  

You have prepared Place for archiving, but you are now assuming that an array of Place will be archived automatically as you store it in NSUserDefaults. It won't be. You have to archive it. The error message is telling you this. The only things that can be saved in NSUserDefaults are objects with property list types: NSArray, NSDictionary, NSString, NSData, NSDate and NSNumber. A Place object is not one of those.

您已经为存档准备好了Place,但是现在假设在NSUserDefaults中存储位置数组时将自动存档。它不会。你得把它存档。错误消息告诉您这一点。在NSUserDefaults中唯一可以保存的是属性列表类型的对象:NSArray、NSDictionary、NSString、NSData、NSDate和NSNumber。地点对象不是其中之一。

Instead of trying to save the array directly, archive it. Now it is an NSData — and that is one of the property list object types:

与其尝试直接保存数组,不如将其存档。现在它是一个NSData -这是属性列表对象类型之一:

let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)

Now you can store myData in NSUserDefaults, because it is an NSData. Of course when you pull it out again you will also have to unarchive it to turn it from an NSData back into an array of Place.

现在可以在NSUserDefaults中存储myData,因为它是NSData。当然,当你再次取出它的时候,你也需要将它从NSData转换回一个位置数组。

EDIT: By the way, it occurs to me, as an afterthought, that your Place class may have to explicitly adopt the NSCoding protocol to get this to work. You seem to have omitted that step.

编辑:顺便说一下,我后来想到,您的Place类可能必须显式地采用NSCoding协议才能使其工作。你似乎漏掉了那一步。

#4


5  

Swift 4

斯威夫特4

We need to serialize our swift object to save it into userDefaults.

我们需要序列化我们的swift对象,将其保存到userDefaults中。

In swift 4 we can use Codable protocol, which makes our life easy on serialization and JSON parsing

在swift 4中,我们可以使用可编码协议,这使我们在序列化和JSON解析方面更容易

Workflow(Save swift object in UserDefaults):

工作流(用户默认保存swift对象):

  1. Confirm Codable protocol to model class(class Place : Codable).
  2. 确认可编码协议到模型类(类位置:可编码)。
  3. Create object of class.
  4. 创建类的对象。
  5. Serialize that class using JsonEncoder class.
  6. 使用JsonEncoder类序列化该类。
  7. Save serialized(Data) object to UserDefaults.
  8. 将序列化(数据)对象保存为UserDefaults。

Workflow(Get swift object from UserDefaults):

工作流程(从用户默认设置中获取swift对象):

  1. Get data from UserDefaults(Which will return Serialized(Data) object)
  2. 从UserDefaults中获取数据(它会返回序列化的数据)
  3. Decode Data using JsonDecoder class
  4. 使用JsonDecoder类解码数据

Swift 4 Code:

斯威夫特4代码:

class Place: Codable {
    var latitude: Double
    var longitude: Double

    init(lat : Double, long: Double) {
        self.latitude = lat
        self.longitude = long
    }

    public static func savePlaces(){
        var placeArray = [Place]()
        let place1 = Place(lat: 10.0, long: 12.0)
        let place2 = Place(lat: 5.0, long: 6.7)
        let place3 = Place(lat: 4.3, long: 6.7)
        placeArray.append(place1)
        placeArray.append(place2)
        placeArray.append(place3)
        let placesData = try! JSONEncoder().encode(placeArray)
        UserDefaults.standard.set(placesData, forKey: "places")
    }

    public static func getPlaces() -> [Place]?{
        let placeData = UserDefaults.standard.data(forKey: "places")
        let placeArray = try! JSONDecoder().decode([Place].self, from: placeData!)
        return placeArray
    }
}

#5


1  

In Swift 4.0+ we can use the type alias Codable which consist of 2 protocols: Decodable & Encodable.

在Swift 4.0+中,我们可以使用包含2个协议的类型别名:可解码的和可编码的。

For convenience, I've created a generic decode and encode methods that are type constrained to Codable:

为了方便起见,我创建了一个通用的解码和编码方法,它们的类型被限制为可编码:

extension UserDefaults {
    func decode<T : Codable>(for type : T.Type, using key : String) -> T? {
        let defaults = UserDefaults.standard
        guard let data = defaults.object(forKey: key) as? Data else {return nil}
        let decodedObject = try? PropertyListDecoder().decode(type, from: data)
        return decodedObject
    }

    func encode<T : Codable>(for type : T, using key : String) {
        let defaults = UserDefaults.standard
        let encodedData = try? PropertyListEncoder().encode(type)
        defaults.set(encodedData, forKey: key)
        defaults.synchronize()
    }
}

Usage - saving an object/array/dictionary:

用法-保存对象/数组/字典:

Let's say we have a custom object:

假设我们有一个自定义对象:

struct MyObject: Codable {
    let counter: Int
    let message: String
}

and we have created an instance from it:

我们从中创建了一个实例:

let myObjectInstance = MyObject(counter: 10, message: "hi there")

Using the the generic extension above we can now save this object as follow:

使用上面的通用扩展,我们现在可以保存这个对象如下:

UserDefaults.standard.encode(for: myObjectInstance, using: String(describing: MyObject))

Saving an array of the same type:

保存相同类型的数组:

UserDefaults.standard.encode(for:[myFirstObjectInstance, mySecondObjectInstance], using: String(describing: MyObject))

Saving a dictionary with that type:

保存具有该类型的字典:

let dictionary = ["HashMe" : myObjectInstance]
UserDefaults.standard.encode(for: dictionary, using: String(describing: MyObject))

Usage - loading an object/array/dictionary:

用法-加载对象/数组/字典:

Loading a single object:

加载一个对象:

let myDecodedObject = UserDefaults.standard.decode(for: MyObject.self, using: String(describing: MyObject))

Loading an array of the same type:

加载相同类型的数组:

let myDecodedObject = UserDefaults.standard.decode(for: [MyObject].self, using: String(describing: MyObject))

Loading a dictionary with that type:

在字典中装入这种类型的词典:

let myDecodedObject = UserDefaults.standard.decode(for: ["HashMe" : myObjectInstance].self, using: String(describing: MyObject))