UserDefaultsは便利なのでよく使うと思いますが,独自クラスを保存する方法が少しテクいのでまとめておきます.
独自クラスはNSObject
とNSCoding
を継承するようにします.
あとは,データの保存時と取り出し時のために,エンコードとデコードの仕方を記述してあげます.
import Foundation.NSObject
class OriginalData: NSObject, NSCoding {
var primaryKey: String
var dataA: Bool
var dataB: Int
var dataC: Double
var dataD: Date? // 初期化する必要のないプロパティ
init(_ primaryKey: String, _ dataA: Bool, _ dataB: Int, _ dataC: Double) {
self.primaryKey = primaryKey
self.dataA = dataA
self.dataB = dataB
self.dataC = dataC
}
required init?(coder: NSCoder) {
primaryKey = (coder.decodeObject(forKey: "primaryKey") as? String) ?? ""
dataA = coder.decodeBool(forKey: "dataA")
dataB = coder.decodeInteger(forKey: "dataB")
dataC = coder.decodeDouble(forKey: "dataC")
}
func encode(with coder: NSCoder) {
coder.encode(primaryKey, forKey: "primaryKey")
coder.encode(dataA, forKey: "dataA")
coder.encode(dataB, forKey: "dataB")
coder.encode(dataC, forKey: "dataC")
}
}
保存の仕方は以下のような感じで,NSKeyedArchiver.archivedData()
を用いてData
型にしてしまいます.
func saveOriginalData() {
let originalData = OriginalData("Apple", true, 123, 3.14)
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: originalData,
requiringSecureCoding: false)
else {
return
}
UserDefaults.standard.set(data, forKey: "OriginalData")
UserDefaults.standard.synchronize()
}
// 配列の場合
func saveOriginalDataArray() {
let array: [OriginalData] = [
OriginalData("Apple", true, 123, 3.14),
OriginalData("Banana", true, 456, 765),
OriginalData("Grape", true, 789, 346)
]
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: array,
requiringSecureCoding: false)
else {
return
}
UserDefaults.standard.set(data, forKey: "OriginalDataArray")
UserDefaults.standard.synchronize()
}
取り出しの仕方は保存時の反対のことをする感じで,NSKeyedUnarchiver.unarchiveTopLevelObjectWithData()
を用いてData
型から独自クラス型にデコードします.
func loadOriginalData() -> OriginalData? {
guard let data = UserDefaults.standard.data(forKey: "OriginalData") else {
return nil
}
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? OriginalData
}
// 配列の場合
func loadOriginalDataArray() -> [OriginalData] {
guard let data = UserDefaults.standard.data(forKey: "OriginalDataArray") else {
return []
}
guard let array = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [OriginalData]
else {
return []
}
return array
}
雑感
今までrequired init?(coder: NSCoder)
ってなんのために書かなきゃいけないの?どうせfatalError
にするのにめんどくさ〜と思っていたのですが,こんな形で活躍するものだったのですね.
豆知識
なんらかの要件変更などで,一度保存してしまった独自クラスに手を加える場合の罠について知見を残しておきます.
どんな風に独自クラス内のプロパティを増やしたり減らしたり名前を変えても,それに対応できるようにinit
のデコードを書き換えれば,取り出しはできるのですが,なぜか独自クラスのクラス名を変更してしまうと取り出しができなくなります.
なので,一度リリースしてしまったアプリで,ユーザがすでに独自クラス型でデータを保存してしまっている場合は,そのクラス名を変更することができませんのでご注意を.