Как сохранить массив объектов в NSUserDefault с помощью swift?
это класс Place
определил:
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
s:
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'
Я уверен, что я заархивировал двойной, есть проблема с процессом сохранения / загрузки
любой ключ ?
5 ответов
С Список Свойств Руководство По Программированию:
если объект property-list является контейнером (то есть массивом или словарем), все объекты, содержащиеся в нем, также должны быть объектами property-list. Если массив или словарь содержат объекты, которые не являются объектами списка свойств, невозможно сохранить и восстановить иерархию данных с помощью различных методов и функций списка свойств.
вам нужно преобразовать объект в и с NSData
экземпляр с помощью NSKeyedArchiver
и NSKeyedUnarchiver
.
например:
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
ниже приведен полный пример кода в 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)")
}
}
Пример Использования:
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
.
в swift 4 мы можем использовать кодируемый протокол, что упрощает нашу жизнь при сериализации и разборе JSON
Workflow (сохранить объект swift в UserDefaults):
- подтвердите кодируемый протокол к классу модели (место класса : кодируемый).
- создать объект класса.
- сериализовать, что класс с использованием класса JsonEncoder.
- сохранить сериализованный (данные) объект в UserDefaults.
рабочий процесс (получить swift объект от UserDefaults):
- получить данные от UserDefaults(который будет возвращать сериализованный (данные) объект)
- декодировать данные с помощью класса JsonDecoder
Swift 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
}
}
вы подготовили место для архивирования, но теперь вы предполагаете, что массив места будет автоматически архивироваться при его хранении в NSUserDefaults. Этого не будет. вы должны заархивировать его. Сообщение об ошибке говорит вам об этом. Единственное, что можно сохранить в NSUserDefaults-это объекты с типами списков свойств: NSArray, NSDictionary, NSString, NSData, NSDate и NSNumber. Объект места не является одним из них.
вместо того, чтобы пытаться сохранить массив прямо,архиве его. Теперь это NSData - и это один из типов объектов списка свойств:
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
теперь вы можете хранить myData
в NSUserDefaults, потому что это NSData. Конечно, когда вы вытащите его снова, вам также придется разархивировать его, чтобы превратить его из NSData обратно в массив места.
редактировать: кстати, мне пришло в голову, что ваш класс Place может явно принять протокол NSCoding чтобы это сработало. Вы, кажется, опустили этот шаг.
в Swift 4.0+ мы можем использовать псевдоним типа возможностью которые состоят из 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)
defaults.synchronize()
}
}
использование - сохранение объекта/массива/словаря:
Допустим, у нас есть пользовательский объект:
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))
сохранение массива того же типа:
UserDefaults.standard.encode(for:[myFirstObjectInstance, mySecondObjectInstance], using: String(describing: MyObject))
сохранение словаря с этим типом:
let dictionary = ["HashMe" : myObjectInstance]
UserDefaults.standard.encode(for: dictionary, using: String(describing: MyObject))
использование - загрузка объекта/массива/словаря:
загрузка одного объекта:
let myDecodedObject = UserDefaults.standard.decode(for: MyObject.self, using: String(describing: MyObject))
загрузка массива того же типа:
let myDecodedObject = UserDefaults.standard.decode(for: [MyObject].self, using: String(describing: MyObject))
загрузка словаря с этим тип:
let myDecodedObject = UserDefaults.standard.decode(for: ["HashMe" : myObjectInstance].self, using: String(describing: MyObject))