LoginSignup
10
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-06

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

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

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

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5