Swift 4のUserDefaultsライブラリ: Default

f:id:vasilyjp:20171020193401j:plain

VASILYのiOSエンジニアにこらすです。

今回のテックブログではiOS・macOS・watchOS・tvOSのUserDefaultsにユーザー設定などを保存するのに便利なラッパーライブラリ Default を作ったので紹介します。

github.com

Defaultとは?

Defaultは、Codableに準拠するカスタムオブジェクトを保存するための拡張機能を提供するライブラリです。プロトコルDefaultStorableを介して、UserDefaultsに以下で説明する新しいインタフェースを提供することで、UserDefaultsを拡張します。 Codableサポート拡張機能とDefaultStorableプロトコル拡張機能いずれかを使うこともできますし、両方つかうこともできます。

Defaultを使うメリットは?

UserDefaultsには、保存したキーをtypoしたり、読み書きしている場所を探すのが難しかったりといった難点があります。 ですが、このDefaultを使って保存されるオブジェクト型を定義すると、DefaultStorable に準拠する型をプロジェクト内で検索することで保存されるデータを追うのが簡単になります。 UserDefaults に格納する専用のオブジェクトを定義すれば、特定のデータを論理的にグループ化することができます。

NSCoding 時代の実装

Swiftでカスタムオブジェクトを宣言した後は、 NSCodingに準拠し、NSObjectから継承し、適切なEncode / Decodeメソッドを実装すれば、 UserDefaultsに直接保存することができるようになります。 これを自前で対応しようとすると、以下のような煩雑なコードが必要になります。

クラスを定義し、 NSCodingに準拠し、必要なDecode / Encodeメソッドを実装する

class VolumeSetting: NSObject, NSCoding {
    let sourceName: String
    let value: Double
    init(sourceName: String, value: Double) {
        self.sourceName = sourceName
        self.value = value
    }
    required init(coder decoder: NSCoder) {
        self.sourceName = decoder.decodeObject(forKey: "sourceName") as? String ?? ""
        self.value = decoder.decodeDouble(forKey: "value")
    }

    func encode(with coder: NSCoder) {
        coder.encode(sourceName, forKey: "sourceName")
        coder.encode(value, forKey: "value")
    }
}

オブジェクトを作成し、 NSKeyedArchiverを使用してインスタンスをDataにアーカイブして保存する

let setting = VolumeSetting(sourceName: "Super Expensive Headphone Amp", value: 0.4)
let encodedData = NSKeyedArchiver.archivedData(withRootObject: setting)
UserDefaults.standard.set(encodedData, forKey: "volume")

読み出すときは NSKeyedUnarchiver を使う

if let data = UserDefaults.standard.data(forKey: "volume"),
   let volumeSetting = NSKeyedUnarchiver.unarchiveObject(with: data) as? VolumeSetting {
   // do something
}

Defaultを使った実装

一方、 Default を使えば、以下のようなシンプルなコードで実現できます。

保存対象のオブジェクトを定義する

DefaultStorable プロトコルに準拠したstructを定義します。

struct VisualSettings: Codable, DefaultStorable {
    let themeName: String
    let backgroundImageURL: URL?
}

保存処理

let settings = VisualSettings(themeName: "bright", backgroundImageURL: URL(string: "https://..."))
settings.storeToDefaults()

読み込み

if let settings = VisualSettings.fetchFromDefaults() {
    // Do something
}

もう一つのメリット

このアプローチのもう一つの利点は、すべてのオブジェクトを一つのファイルに定義することで、UserDefaults に格納されるものを非常に簡単に見ることができることです。

Defaultの設計について

UserDefaultsにカスタムオブジェクトを保存するためには、そのオブジェクトはNSCodingに準拠する必要があります。 NSCodingに準拠するにはDecoding / Encodingメソッドを実装する必要があり、少し手間がかかります。 一方、嬉しいことにSwiftのData型がNSCodingに準拠しています。オブジェクトをDataに変換する方法を見つけることができれば、それをUserDefaultsに格納することができます。 Swift 4から追加されたCodableプロトコルは簡単にDataに変換する事ができます。 Swift 4以降のプロジェクトであれば、Codableに準拠したモデルオブジェクトを作ることが多くなると思います。このライブラリは、そういうCodableプロトコルに準拠したオブジェクトをUserDeafaultsに読み書きすることができるようになっています。

結構シンプルですね!

まとめ

Default は、非常に軽くてシンプルなカスタムオブジェクトを扱う UserDefaultsのラッパーです。 GitHubに公開してあるので、Default をインストールして遊んでみたい場合は、Carthage か CocoaPods を使って試してみてください。

VASILYでは、OSSなどSwift 4での開発に興味があるエンジニアを募集しています。ぜひオフィスに遊びに来てください。

ー にこらす 👍