LoginSignup
26
26

More than 5 years have passed since last update.

NSUserDefaults で struct を保存(?)する方法

Last updated at Posted at 2015-08-27

序言

Swift で開発するとき、基本どうしても class が必要なとき(例えば継承が必要なときなど)以外はなるべく struct で何とかしよう、というのが公式のガイドラインですが、でも実際 Cocoa や UIKit フレームワークがまだ完全に Swift に最適化できていないのもまた実情です。例えば、ユーザ設定を NSUserDefaults で保存したい時があるでしょう。ユーザ設定なら基本決まった構造だけで、継承とかも考えなくていいからまさに struct を利用するのにとても適しているのですが、 NSUserDefaults はそもそもオブジェクトしか保存できないのが難点です。struct は AnyObject に従わないので、そのままでは保存できません。

サンプル

例えば、下記のような設定があるとします:

class ViewController: UIViewController {

    var settings: UserSettings

    init() {
        self.settings = UserSettings(a: 0, b: false, c: "NO")

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
struct UserSettings {

    var a: Int
    var b: Bool
    var c: String

}

上記のように、ViewController クラスの中にユーザ設定構造体を保存している、という非常にシンプルなソースコードです。他になにもやっていませんがまあ適当に中に view の設定も適当にやっていると考えてください。

class を使う方法

まあぶっちゃけオブジェクトしか保存できないなら、セーブ・ロードするときに一時的に class を作っちゃえばいいじゃないか、というのが一番最初に思いつく方法?ではないかな?というわけで早速作ってみます

struct UserSettings {

    var a: Int
    var b: Bool
    var c: String

}

class UserSettingsObject {
    var a: Int
    var b: Bool
    var c: String

    // すでに設定があれば流用します
    init(settings: UserSettings) {
        self.a = settings.a
        self.b = settings.b
        self.c = settings.c
    }

    // まだ設定が作られていない場合はデフォルト設定を作ります
    init() {
        self.a = 0
        self.b = false
        self.c = "NO"
    }
}

class ViewController: UIViewController {

    let userDefaults = NSUserDefaults.standardUserDefaults()
    var settings: UserSettings

    init() {

        let settingsObject = self.userDefaults.valueForKey("SaveData") as? UserSettingsObject ?? UserSettingsObject()
        self.settings = UserSettings(a: settingsObject.a, b: settingsObject.b, c: settingsObject.c)

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func save() {
        let settingObject = UserSettingsObject(settings: self.settings)
        self.userDefaults.setValue(settingObject, forKey: "SaveData")
    }

}

とこんな感じになります。ViewController を初期化するとき、UserSettings を初期化するためにはまず UserSettingsObject を作ります。この時すでに NSUserDefaults で保存されたらそれを取り出しますが、なければデフォルト設定で作ります。そして保存するときも現在の UserSettings から UserSettingsObject を作って NSUserDefaults で保存します。でも正直この方法は面倒ですよね、今は設定が a、b、c の3つだけだからまだ比較的に楽ですが、数が増えてくると非常に大変なことになりかねません。それにメモリ管理の面から見てもあまりよろしくなさそうです。というわけこれを少し改良して、class ではなく NSDictionary を使う方法に転換しました。

NSDictionary を利用する方法

まあオブジェクトである点は class とは全く同じですので、ある意味 class と大差ない気がしなくもないのですが…まあ少しくらいはソースコードは楽になります。そして考え方は一緒です、セーブするときは今の struct から NSDictionary オブジェクトを作って保存し、ロードするときは NSUserDefaults から NSDictionary を引っ張りだして作るか、なければデフォルト値で作ります。

struct UserSettings {

    var a: Int
    var b: Bool
    var c: String

    static func userSettingsFromNSDictionary(dictionary: NSDictionary?) -> UserSettings {
        let aValue = dictionary?["a"] as? Int ?? 0
        let bValue = dictionary?["b"] as? Bool ?? false
        let cValue = dictionary?["c"] as? String ?? "NO"

        return UserSettings(a: aValue, b: bValue, c: cValue)
    }

    func nsdictionaryForNSUserDefaults() -> NSDictionary {
        let dictionary: NSDictionary = [
            "a": self.a,
            "b": self.b,
            "c": self.c,
        ]
        return dictionary
    }

}

class ViewController: UIViewController {

    let userDefaults = NSUserDefaults.standardUserDefaults()
    var settings: UserSettings

    init() {

        let settingsDictionary = self.userDefaults.valueForKey("SaveData") as? NSDictionary
        self.settings = UserSettings.userSettingsFromNSDictionary(settingsDictionary)

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func save() {
        let settingDictionary = self.settings.nsdictionaryForNSUserDefaults()
        self.userDefaults.setValue(settingDictionary, forKey: "SaveData")
    }

}

とこんな感じになります。class を使う方法と比べて、わざわざ新しく class を追加定義しなくて済む上、必要なものは全て struct の中で全部定義できちゃうからメンテナンスの時も比較的に楽になるかと思います。そして何より、NSDictionary は中身の key-value ペアは定められていないため、例えば今後バージョンアップで新しい設定項目を追加しても、すでに保存されたユーザ設定の中にその key-value ペアが見当たらないだけで新しく作れるし、新しく作ったらまた保存するとき追加で保存されますので、柔軟性も class を使うよりいいのではないかと思います。

まとめ

というわけで struct で設定されているユーザ設定は NSDictionary を利用して NSUserDefaults で保存するのが今のところ自分が感じた一番便利な方法かと思いますが、もし他になにかいい方法があれば教えて下さいm(_ _)m

26
26
8

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
26
26