Swift：独自クラスをUserDefaultsで保存する

UserDefaultsは便利なのでよく使うと思いますが，独自クラスを保存する方法が少しテクいのでまとめておきます．

独自クラスは`NSObject`と`NSCoding`を継承するようにします．
あとは，データの保存時と取り出し時のために，エンコードとデコードの仕方を記述してあげます．

```swift:独自クラス例
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`型にしてしまいます．

```swift:保存
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`型から独自クラス型にデコードします．

```swift:取り出し
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`のデコードを書き換えれば，取り出しはできるのですが，なぜか独自クラスの**クラス名を変更**してしまうと取り出しができなくなります．
なので，一度リリースしてしまったアプリで，ユーザがすでに独自クラス型でデータを保存してしまっている場合は，そのクラス名を変更することができませんのでご注意を．