アプリ内にデータを保存するUserDefaultsの使い方を解説します。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.2.4
【iOS】13.5
【macOS】Catalina バージョン 10.15.5
UserDefaultsとは?
UserDefaultsは、アプリ内にデータを保存する仕組みです。
アプリの設定やユーザー設定などの軽量データを扱う操作を想定した仕組みで、大量のデータを扱うには不向きです。
アプリを終了してもデータは消えませんが、アプリを削除するとデータも消えてしまいますので、重要なデータをUserDefaultsのみに持たせるのは控えたほうがいいでしょう。
基本的な使い方
操作の基本は標準UserDefaultsオブジェクト(userDefaults.standard)のインスタンスメソッドで操作します。
データはキーと値のペアで保存します。
キーは常に文字列で、保存できる値は次のいずれかのデータ型になります。
Data、String、Number(Int、Float、Double)、Date、Array、Dictionary、Bool
保存(追加・更新)
保存にはset(_:forKey:)メソッドを使います。
値の型に応じてオーバーロードメソッドが呼ばれます。
同じキーで更新すると前の値が更新されます。
1 2 3 |
userDefaults.standard.set(値, forKey: "キー") |
読み取り
UserDefaultsからデータを読み取るには、型に応じた専用メソッドか汎用メソッドを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// 専用メソッド userDefaults.standard.integer(forKey: "キー") -> Int // Int型 userDefaults.standard.float(forKey: "キー") -> Float // Float型 userDefaults.standard.double(forKey: "キー") -> Double // Double型 userDefaults.standard.bool(forKey: "キー") -> Bool // Bool型 userDefaults.standard.url(forKey: "キー") -> URL? // URL型 userDefaults.standard.string(forKey: "キー") -> String? // 文字列型 userDefaults.standard.stringArray(forKey: "キー") -> [String]? // 文字型配列 userDefaults.standard.dictionary(forKey: "キー") -> [String : Any]? // 辞書型 userDefaults.standard.data(forKey: "キー") -> Data? // Data型 /// 汎用メソッド userDefaults.standard.object(forKey: "キー") -> Any? // 汎用型 userDefaults.standard.array(forKey: "キー") -> [Any]? // 汎用配列 |
専用メソッドが無い型の読み取り
専用メソッドが無い型を読み取る場合は、汎用型メソッドを使って一旦Any型で読み込んだ後に、キャスト演算子(as?)で対象の型に変換します。
例えば、専用メソッドの無いDate型は次のように読み込みます。
1 2 3 |
let value = UserDefaults.standard.object(forKey: "キー") as? Date |
キーに対する値が無い場合のデフォルト値の設定
キーに対する値が無い場合、基本的にはnilが返るので、次にようにnil合体演算子(??)を使ってデフォルト値を指定できます。
1 2 3 |
let value = userDefaults.standard.string(forKey: "キー") ?? "デフォルト値" |
しかし数値型とBool型の読み取りメソッドでは、値が無い場合のデフォルト値として0またはfalseが返りますので、値が存在していたのか否かの見分けがつきません。
この場合、汎用型のobject(forKey:)メソッドを利用すると良いです。
汎用型メソッドはキーに対する値が無い場合にnilを返します。
次は、Int型で値が設定されていなかった場合に、デフォルト値99を設定する例です。
1 2 3 |
let value = UserDefaults.standard.object(forKey: "キー") as? Int ?? 99 |
削除
特定のキーのみ削除
1 2 3 |
UserDeafaults.standard.removeObject(forKey: "キー") |
全てのキーを削除
1 2 3 4 |
let appDomain = Bundle.main.bundleIdentifier UserDefaults.standard.removePersistentDomain(forName: appDomain!) |
使用例
数値型
読み取りメソッドによって、取得される数値の型が決まります。
キーに対応する値が無い場合は、0が返ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/// 保存 let valueToSave = 123 UserDefaults.standard.set(valueToSave, forKey: "Numeric") /// 読み込み let valueInt = UserDefaults.standard.integer(forKey: "Numeric") let valueFloat = UserDefaults.standard.float(forKey: "Numeric") let valueDouble = UserDefaults.standard.double(forKey: "Numeric") print(valueInt) // 123 print(valueFloat) // 123.0 print(valueDouble) // 123.0 /// 読み込み(キーに対する値が無い場合) let valueInt2 = UserDefaults.standard.integer(forKey: "Numeric2") print(valueInt2) // 0 /// 読み込み(値が無い場合にデフォルト値を設定する) let valueInt3 = UserDefaults.standard.object(forKey: "Numeric3") as? Int ?? 99 print(valueInt3) // 99 |
Bool型
キーに対応する値が無い場合は、falseが返ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/// 保存 let valueToSave = true UserDefaults.standard.set(valueToSave, forKey: "Boolean") /// 読み込み let value = UserDefaults.standard.bool(forKey: "Boolean") print(value) // true /// 読み込み(キーに対する値が無い場合) let value2 = UserDefaults.standard.bool(forKey: "Boolean2") print(value2) // false /// 読み込み(値が無い場合にデフォルト値を設定する) let value3 = UserDefaults.standard.object(forKey: "Boolean3") as? Bool ?? true print(value3) // true |
String型
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// 保存 let valueToSave = "カピ通信" UserDefaults.standard.set(valueToSave, forKey: "String") /// 読み込み let value = UserDefaults.standard.string(forKey: "String") print(value!) // カピ通信 /// 読み込み(値が無い場合にデフォルト値を設定する) let value2 = UserDefaults.standard.string(forKey: "String2") ?? "デフォルト値" print(value2) // デフォルト値 |
Date型
専用メソッドの無い型は、一旦汎用型のobject(forKey:)メソッドで読み込んでから、対象の型にキャストします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// 保存 let valueToSave = Date() UserDefaults.standard.set(valueToSave, forKey: "Date") /// 読み込み let value = UserDefaults.standard.object(forKey: "Date") as? Date print(value!) // 2020-07-09 12:51:55 +0000 /// 読み込み(値が無い場合に現在時刻を設定する) let value2 = UserDefaults.standard.object(forKey: "Date2") as? Date ?? Date() print(value2) // 2020-07-09 12:51:55 +0000 |
文字列配列
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// 保存 let valueToSave = ["カピ", "通信"] UserDefaults.standard.set(valueToSave, forKey: "StringArray") /// 読み込み let value = UserDefaults.standard.stringArray(forKey: "StringArray") print(value!) // ["カピ", "通信"] /// 読み込み(値が無い場合に空配列を設定する) let value2 = UserDefaults.standard.stringArray(forKey: "StringArray2") ?? [String]() print(value2) // [] |
数値配列
文字列以外の配列は専用メソッドが無いので、一旦汎用型のobject(forKey:)メソッドで読み込んでから、対象の型にキャストします。
※object(forKey:)の代わりにarray(forKey:)でも動作します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// 保存 let valueToSave = [1, 2] UserDefaults.standard.set(valueToSave, forKey: "NumericArray") /// 読み込み let value = UserDefaults.standard.object(forKey: "NumericArray") as? [Int] print(value!) // [1, 2] /// 読み込み(値が無い場合に空配列を設定する) let value2 = UserDefaults.standard.object(forKey: "NumericArray2") as? [Int] ?? [Int]() print(value2) // [] |
構造体・クラス
構造体・クラスは一旦Data型(バイト列を扱う型)に変換してから保存します。
変換する為には、対象の構造体・クラスをCodableプロトコルに準拠させる必要があります。
1 2 3 4 5 6 |
struct User: Codable { let name: String let age: Int } |
以下は、構造体をJSON形式のData型に変換してから保存し、読み込み時に元の構造体に戻す例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/// 保存 let valueToSave = User(name: "capibara", age: 20) let encoder = JSONEncoder() if let encodedValue = try? encoder.encode(valueToSave) { UserDefaults.standard.set(encodedValue, forKey: "user") } /// 読み込み if let savedValue = UserDefaults.standard.data(forKey: "user") { let decoder = JSONDecoder() if let value = try? decoder.decode(User.self, from: savedValue) { print(value) // User(name: "capibara", age: 20) } } else { /// 値がない場合の処理 } |
読み込み時にJSON型から構造体に変換する時の引数の記述"User.self"に若干違和感を覚えますが、変換する構造体のタイプ自体を参照する意味があります。
対象のデータが構造体の配列の場合は、次のように記述します。
1 2 3 |
if let value = try? decoder().decode([User].self, from: savedValue) { |