これは私が定義したクラス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の初心者ですが、手伝ってもらえますか?
第2版
データを保存する解決策を見つけました:
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をアーカイブしたと確信しています。保存/読み込みプロセスに問題があります
どんな手掛かり ?
プロパティリストオブジェクトがコンテナ(つまり、配列または辞書)である場合、それに含まれるすべてのオブジェクトもプロパティリストオブジェクトである必要があります。配列または辞書にプロパティリストオブジェクトではないオブジェクトが含まれている場合、さまざまなプロパティリストメソッドおよび関数を使用してデータの階層を保存および復元することはできません。
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 4
Swift
オブジェクトをシリアル化して、userDefaults
に保存する必要があります。
Swift 4ではCodableプロトコルを使用できます。これにより、シリアル化とJSON解析が楽になります
ワークフロー(Save Swift UserDefaultsのオブジェクト):
ワークフロー(Get Swift UserDefaultsからのオブジェクト):
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
}
}
以下は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.0+では、2つのプロトコルで構成されるタイプエイリアス Codable 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.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))
使用法-オブジェクト/配列/辞書の読み込み:
単一のオブジェクトをロードする:
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))
Placeをアーカイブ用に準備しましたが、NSUserDefaultsに保存するときにPlaceの配列が自動的にアーカイブされることを想定しています。ありません。 あなたはアーカイブする必要があります。エラーメッセージはこれを伝えています。 NSUserDefaultsに保存できるのは、NSArray、NSDictionary、NSString、NSData、NSDateおよびNSNumberのプロパティリストタイプを持つオブジェクトのみです。 Placeオブジェクトはそれらの1つではありません。
配列を直接保存しようとする代わりに、archiveそれを保存します。 NSDataになりました。これは、プロパティリストオブジェクトタイプの1つです。
let myData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
NSDataであるため、NSUserDefaultsにmyData
を保存できるようになりました。もちろん、それを再び引き出すときは、NSDataからPlaceの配列に戻すために、アーカイブを解除する必要もあります。
[〜#〜] edit [〜#〜]:ところで、思いついたように、PlaceクラスはNSCodingプロトコルを明示的に採用してこれを機能させる必要があるかもしれません。あなたはそのステップを省略しているようです。
以下は私が使用するコードです。これが役立つことを願っています。
スポーツがオブジェクトであると考えてください。最初に、以下のようにCodable 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()は保存されたデータを返します。