如何使用 swift 将对象数组保存到 NSUserDefault?
How to save an array of objects to NSUserDefault with swift?
这是一个classPlace
我定义的:
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")
}
}
这就是我试图保存 Place
数组的方式:
var placesArray = [Place]
//...
func savePlaces() {
NSUserDefaults.standardUserDefaults().setObject(placesArray, forKey: "places")
println("place saved")
}
没用,这是我在控制台上得到的:
Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')
我是 iOS 的新手,你能帮帮我吗?
第二版
我找到了保存数据的解决方案:
func savePlaces(){
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
NSUserDefaults.standardUserDefaults().setObject(myData, forKey: "places")
println("place saved")
}
但是我在使用此代码加载数据时遇到错误:
let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData
if placesData != nil {
placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData!) as [Place]
}
错误是:
[NSKeyedUnarchiver decodeDoubleForKey:]: value for key (latitude) is not a double number'
我很确定我存档了 Double,saving/loading 过程有问题
有什么线索吗?
您已准备好用于存档的 Place,但您现在假设当您将 Place 数组存储在 NSUserDefaults 中时,它会自动存档。不会的。 您必须将其存档。错误消息告诉您这一点。唯一可以保存在 NSUserDefaults 中的是 属性 列表类型的对象:NSArray、NSDictionary、NSString、NSData、NSDate 和 NSNumber。 Place 对象不是其中之一。
与其尝试直接保存数组,不如存档。现在它是一个 NSData — 这是 属性 列表对象类型之一:
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
现在您可以将 myData
存储在 NSUserDefaults 中,因为它是一个 NSData。当然,当您再次将其拉出时,您还必须取消存档以将其从 NSData 变回 Place 数组。
编辑:顺便说一下,事后我突然想到,您的 Place class 可能必须明确采用 NSCoding 协议才能实现工作。你好像省略了那一步。
来自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.
您需要使用 NSKeyedArchiver
和 NSKeyedUnarchiver
.
将对象与 NSData
实例相互转换
例如:
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…
}
}
}
Swift 3 & 4
以下是Swift3&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)")
}
}
使用示例:
savePlaces()
loadPlaces()
控制台输出:
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'
Swift 4
我们需要序列化我们的 swift
对象以将其保存到 userDefaults
。
In swift 4 we can use Codable protocol, which makes our life easy on serialization and JSON parsing
工作流程(在 UserDefaults 中保存 swift 对象):
- 确认 Codable 协议模型 class(class 地点:Codable)。
- 创建 class 的对象。
- 使用 JsonEncoder class 序列化 class。
- 将序列化(数据)对象保存到 UserDefaults。
工作流程(从 UserDefaults 获取 swift 对象):
- 从 UserDefaults 获取数据(这将 return 序列化(数据)对象)
- 使用 JsonDecoder 解码数据 class
Swift4码:
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
}
}
在 Swift 4.0+ 中,我们可以使用类型别名 Codable,它由 2 个协议组成:Decodable 和 Encodable。
为方便起见,我创建了一个通用的解码和编码方法,类型限制为 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)
}
}
用法 - 保存 object/array/dictionary:
假设我们有一个自定义对象:
struct MyObject: Codable {
let counter: Int
let message: String
}
并且我们已经从中创建了一个实例:
let myObjectInstance = MyObject(counter: 10, message: "hi there")
使用上面的通用扩展,我们现在可以按如下方式保存此对象:
UserDefaults.standard.encode(for: myObjectInstance, using: String(describing: MyObject.self))
正在保存相同类型的数组:
UserDefaults.standard.encode(for:[myFirstObjectInstance, mySecondObjectInstance], using: String(describing: MyObject.self))
正在保存该类型的字典:
let dictionary = ["HashMe" : myObjectInstance]
UserDefaults.standard.encode(for: dictionary, using: String(describing: MyObject.self))
用法 - 加载一个 object/array/dictionary:
加载单个对象:
let myDecodedObject = UserDefaults.standard.decode(for: MyObject.self, using: String(describing: MyObject.self))
正在加载相同类型的数组:
let myDecodedObject = UserDefaults.standard.decode(for: [MyObject].self, using: String(describing: MyObject.self))
正在加载该类型的字典:
let myDecodedObject = UserDefaults.standard.decode(for: ["HashMe" : myObjectInstance].self, using: String(describing: MyObject.self))
以下是我使用的代码。希望这会有所帮助。
考虑 运动 是对象。我们需要确认 Codable class 对象 class 如下。
class Sport: Codable {
var id: Int!
var name: String!
}
然后我们可以简单地使用以下代码来保存使用 UserDefaults 的对象数组。
func saveSports(_ list:[Sport]) {
UserDefaults.standard.set(try? PropertyListEncoder().encode(list), forKey:"KEY")
UserDefaults.standard.synchronize()
}
现在我们可以简单地将对象列表作为参数传递并使用方法 saveSports()
进行保存
要获取保存的数据,我们可以使用以下代码。
func getSports() -> [Sport]? {
if let data = UserDefaults.standard.value(forKey:"KEY") as? Data {
let decodedSports = try? PropertyListDecoder().decode([Sport].self, from: data)
return decodedSports
}
return nil
}
方法getSports()returns保存的数据。
Swift 5
将以下 属性 放入您的一位经理中:
class UserSessionManager
{
// MARK:- Properties
public static var shared = UserSessionManager()
var places: [Place]
{
get
{
guard let data = UserDefaults.standard.data(forKey: "places") else { return [] }
return (try? JSONDecoder().decode([Place].self, from: data)) ?? []
}
set
{
guard let data = try? JSONEncoder().encode(newValue) else { return }
UserDefaults.standard.set(data, forKey: "places")
}
}
// MARK:- Init
private init(){}
}
使用
var placesArray: [Place] = []
placesArray.append(Place(latitude: 12, longitude: 21, name: "place 1"))
// save it easily!
UserSessionManager.shared.places = placesArray
// load it easily
let mySavedPlaces = UserSessionManager.shared.places
“Mobile Den”版本运行良好,但某些类型在 12.0 版本中已弃用。我刷新这个class。 SWIFT 5
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"))
do {
let placesData = try NSKeyedArchiver.archivedData(withRootObject: placesArray, requiringSecureCoding: false)
UserDefaults.standard.set(placesData, forKey: "places")
} catch {
print(error.localizedDescription)
}
}
func loadPlaces() {
guard let placesData = UserDefaults.standard.object(forKey: "places") as? NSData else {
print("'places' not found in UserDefaults")
return
}
do {
guard let placesArray = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(placesData as Data) as? [Place] else { return }
for place in placesArray {
print("place.latitude: \(place.latitude)")
print("place.longitude: \(place.longitude)")
print("place.name: \(place.name)")
}
} catch {
print(error.localizedDescription)
}
}
这是一个classPlace
我定义的:
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")
}
}
这就是我试图保存 Place
数组的方式:
var placesArray = [Place]
//...
func savePlaces() {
NSUserDefaults.standardUserDefaults().setObject(placesArray, forKey: "places")
println("place saved")
}
没用,这是我在控制台上得到的:
Property list invalid for format: 200 (property lists cannot contain objects of type 'CFType')
我是 iOS 的新手,你能帮帮我吗?
第二版
我找到了保存数据的解决方案:
func savePlaces(){
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
NSUserDefaults.standardUserDefaults().setObject(myData, forKey: "places")
println("place saved")
}
但是我在使用此代码加载数据时遇到错误:
let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData
if placesData != nil {
placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData!) as [Place]
}
错误是:
[NSKeyedUnarchiver decodeDoubleForKey:]: value for key (latitude) is not a double number'
我很确定我存档了 Double,saving/loading 过程有问题
有什么线索吗?
您已准备好用于存档的 Place,但您现在假设当您将 Place 数组存储在 NSUserDefaults 中时,它会自动存档。不会的。 您必须将其存档。错误消息告诉您这一点。唯一可以保存在 NSUserDefaults 中的是 属性 列表类型的对象:NSArray、NSDictionary、NSString、NSData、NSDate 和 NSNumber。 Place 对象不是其中之一。
与其尝试直接保存数组,不如存档。现在它是一个 NSData — 这是 属性 列表对象类型之一:
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
现在您可以将 myData
存储在 NSUserDefaults 中,因为它是一个 NSData。当然,当您再次将其拉出时,您还必须取消存档以将其从 NSData 变回 Place 数组。
编辑:顺便说一下,事后我突然想到,您的 Place class 可能必须明确采用 NSCoding 协议才能实现工作。你好像省略了那一步。
来自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.
您需要使用 NSKeyedArchiver
和 NSKeyedUnarchiver
.
NSData
实例相互转换
例如:
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…
}
}
}
Swift 3 & 4
以下是Swift3&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)")
}
}
使用示例:
savePlaces()
loadPlaces()
控制台输出:
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'
Swift 4
我们需要序列化我们的 swift
对象以将其保存到 userDefaults
。
In swift 4 we can use Codable protocol, which makes our life easy on serialization and JSON parsing
工作流程(在 UserDefaults 中保存 swift 对象):
- 确认 Codable 协议模型 class(class 地点:Codable)。
- 创建 class 的对象。
- 使用 JsonEncoder class 序列化 class。
- 将序列化(数据)对象保存到 UserDefaults。
工作流程(从 UserDefaults 获取 swift 对象):
- 从 UserDefaults 获取数据(这将 return 序列化(数据)对象)
- 使用 JsonDecoder 解码数据 class
Swift4码:
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
}
}
在 Swift 4.0+ 中,我们可以使用类型别名 Codable,它由 2 个协议组成:Decodable 和 Encodable。
为方便起见,我创建了一个通用的解码和编码方法,类型限制为 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)
}
}
用法 - 保存 object/array/dictionary:
假设我们有一个自定义对象:
struct MyObject: Codable {
let counter: Int
let message: String
}
并且我们已经从中创建了一个实例:
let myObjectInstance = MyObject(counter: 10, message: "hi there")
使用上面的通用扩展,我们现在可以按如下方式保存此对象:
UserDefaults.standard.encode(for: myObjectInstance, using: String(describing: MyObject.self))
正在保存相同类型的数组:
UserDefaults.standard.encode(for:[myFirstObjectInstance, mySecondObjectInstance], using: String(describing: MyObject.self))
正在保存该类型的字典:
let dictionary = ["HashMe" : myObjectInstance]
UserDefaults.standard.encode(for: dictionary, using: String(describing: MyObject.self))
用法 - 加载一个 object/array/dictionary:
加载单个对象:
let myDecodedObject = UserDefaults.standard.decode(for: MyObject.self, using: String(describing: MyObject.self))
正在加载相同类型的数组:
let myDecodedObject = UserDefaults.standard.decode(for: [MyObject].self, using: String(describing: MyObject.self))
正在加载该类型的字典:
let myDecodedObject = UserDefaults.standard.decode(for: ["HashMe" : myObjectInstance].self, using: String(describing: MyObject.self))
以下是我使用的代码。希望这会有所帮助。
考虑 运动 是对象。我们需要确认 Codable class 对象 class 如下。
class Sport: Codable {
var id: Int!
var name: String!
}
然后我们可以简单地使用以下代码来保存使用 UserDefaults 的对象数组。
func saveSports(_ list:[Sport]) {
UserDefaults.standard.set(try? PropertyListEncoder().encode(list), forKey:"KEY")
UserDefaults.standard.synchronize()
}
现在我们可以简单地将对象列表作为参数传递并使用方法 saveSports()
进行保存要获取保存的数据,我们可以使用以下代码。
func getSports() -> [Sport]? {
if let data = UserDefaults.standard.value(forKey:"KEY") as? Data {
let decodedSports = try? PropertyListDecoder().decode([Sport].self, from: data)
return decodedSports
}
return nil
}
方法getSports()returns保存的数据。
Swift 5
将以下 属性 放入您的一位经理中:
class UserSessionManager
{
// MARK:- Properties
public static var shared = UserSessionManager()
var places: [Place]
{
get
{
guard let data = UserDefaults.standard.data(forKey: "places") else { return [] }
return (try? JSONDecoder().decode([Place].self, from: data)) ?? []
}
set
{
guard let data = try? JSONEncoder().encode(newValue) else { return }
UserDefaults.standard.set(data, forKey: "places")
}
}
// MARK:- Init
private init(){}
}
使用
var placesArray: [Place] = []
placesArray.append(Place(latitude: 12, longitude: 21, name: "place 1"))
// save it easily!
UserSessionManager.shared.places = placesArray
// load it easily
let mySavedPlaces = UserSessionManager.shared.places
“Mobile Den”版本运行良好,但某些类型在 12.0 版本中已弃用。我刷新这个class。 SWIFT 5
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"))
do {
let placesData = try NSKeyedArchiver.archivedData(withRootObject: placesArray, requiringSecureCoding: false)
UserDefaults.standard.set(placesData, forKey: "places")
} catch {
print(error.localizedDescription)
}
}
func loadPlaces() {
guard let placesData = UserDefaults.standard.object(forKey: "places") as? NSData else {
print("'places' not found in UserDefaults")
return
}
do {
guard let placesArray = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(placesData as Data) as? [Place] else { return }
for place in placesArray {
print("place.latitude: \(place.latitude)")
print("place.longitude: \(place.longitude)")
print("place.name: \(place.name)")
}
} catch {
print(error.localizedDescription)
}
}