「UserDefaults」とは?
ユーザーのデフォルトデータベースへのインターフェイスです。
アプリの起動時にキーと値のペアを永続的に保存します。
一言でいうと「簡単に使えるキーバリュー型のDB」です。
UserDefaultsを使う目的
アプリ起動時にデフォルトの状態や動作方法を決定するために使います。
キーと値のペアを簡単に永続化できるため、ちょっとした設定の保存などに向いています。
アプリを削除するとUserDefaults内のデータも全て消えるため、重要なデータをUserDefaultsのみに持たせるべきではありません。
UserDefaultsの使用例
- ユーザーが好みの測定単位を指定する(cmやmなど)
- ユーザーがメディアの初期再生速度を指定する
環境
- Swift:4.2.1
- Xcode:10.1 (10B61)
CRUD操作
キーは文字列である必要があります。
追加・更新
追加と更新は同じメソッドを使います。
値に応じたオーバーロードメソッドが呼ばれます。
UserDefaults.standard.set({値}, forKey: {キー})
取得
追加・更新時と異なり、オーバーロードメソッドがないため、値によってメソッドを呼び分ける必要があります。
UserDefaults.standard.object(forKey: {キー})
UserDefaults.standard.string(forKey: {キー})
UserDefaults.standard.array(forKey: {キー})
UserDefaults.standard.dictionary(forKey: {キー})
UserDefaults.standard.data(forKey: {キー})
UserDefaults.standard.stringArray(forKey: {キー})
UserDefaults.standard.integer(forKey: {キー})
UserDefaults.standard.float(forKey: {キー})
UserDefaults.standard.double(forKey: {キー})
UserDefaults.standard.bool(forKey: {キー})
UserDefaults.standard.url(forKey: {キー})
用意されていない型(例: Date
型)を取得したい場合、まず UserDefaults.standard.object(forKey:)
で取得し、対象の型でキャストする必要があります。
private func load(key: String) -> Date? {
let value = UserDefaults.standard.object(forKey: key)
guard let date = value as? Date else {
return nil
}
return date
}
削除
削除は値によってメソッドを呼び分ける必要がありません。
UserDefaults.standard.removeObject(forKey: {キー})
全削除
UserDefaultsの値を全削除する処理は標準で備わっていません。
以下のように UserDefaults
クラスを拡張することで実現できます。
extension UserDefaults {
func removeAll() {
dictionaryRepresentation().forEach { removeObject(forKey: $0.key) }
}
}
UserDefaults.standard.removeAll()
全削除処理を用意すると、UserDefaultsの単体テスト時に便利です。
ただし、簡単に実装できるのにAppleが用意していない理由は、不用意に実行して値が全削除されないようにするためかもしれません。
私はUserDefaultsを拡張せず、クライアントクラスを噛ませてその中で全削除処理を実装しています。
(いつか別記事で紹介したいです)
応用
自作した構造体などを扱う
自作した構造体などは、そのままだとUserDefaultsに追加できません。
例えば、以下のような構造体があるとします。
struct Monster: Equatable {
let name: String
let description: String
}
そのままUserDefaultsに追加しようとすると、以下の実行時エラーが発生します。
let monster = Monster(name: "uhooi", description: "ゆかいな みどりの せいぶつ。")
UserDefaults.standard.set(monster, forKey: key)
[User Defaults] Attempt to set a non-property-list object
自作した構造体をUserDefaultsで扱う方法はいろいろあると思いますが、 Data
型に変換するとかんたんです。
まず、 Data
型に変換できるようにするため、自作した構造体を Codable
プロトコルに準拠させます。
extension Monster: Codable {}
追加する場合、 Data
型に変換してセットすればOKです。
let monster = Monster(name: "uhooi", description: "ゆかいな みどりの せいぶつ。")
// `JSONEncoder` で `Data` 型へエンコードし、UserDefaultsに追加する
let jsonEncoder = JSONEncoder()
jsonEncoder.keyEncodingStrategy = .convertToSnakeCase
guard let data = try? jsonEncoder.encode(monster) else {
return
}
UserDefaults.standard.set(data, forKey: key)
取得する場合は、UserDefaultsから Data
型として取得し、自作した構造体に変換し直せばOKです。
// `JSONDecoder` で `Data` 型を自作した構造体へデコードする
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
guard let data = UserDefaults.standard.data(forKey: key),
let monster = try? jsonDecoder.decode(Monster.self, from: data) else {
return
}
これで自作した構造体などをUserDefaultsで扱えるようになりました!
おまけ
synchronize()
は使うべきではない
以前は UserDefaults.synchronize()
メソッドを呼び出さないと保存されないことがあったようです。
しかし、現在は「this method is unnecessary and shouldn't be used. (このメソッドは不要であり、使うべきではない)」とのことです。
value
は使わない
NSObject
の拡張メソッドである open func value(forKey key: String) -> Any?
と open func setValue(_ value: Any?, forKey key: String)
のことです。
こちらは公式ドキュメントに「使うべきではない」と明確に書かれているかはわかりませんが、型が失われるので使うべきではないと考えます。
UserDefaultsへの保存時も取得時も、型を保ったまま処理しましょう。
- UserDefaults.standard.setValue(data, forKey: key)
+ UserDefaults.standard.set(data, forKey: key)
- UserDefaults.standard.value(forKey: key) as? Data
+ UserDefaults.standard.data(forKey: key)
おわりに
iOSアプリではキーバリュー型のDBが非常に簡単に使えることがわかりました。
保存先をinfo.plistやRealmに変更しても動作するように、プロトコルを定義して使うとさらにいいです。
その際、プロトコルの名前は TempStorageProtocol
のように、特定のDBに依存しない名前を付けると、保存先の変更時に呼び出し側を変更せずに済みます。
プロトコルを噛ませるとテストもしやすいです。