「UserDefaults消したーーーーい」と思って調べるとこのようなコードと出会った。
let appDomain = Bundle.main.bundleIdentifier
UserDefaults.standard.removePersistentDomain(forName: appDomain!)
なんとなくBundle Identifierを取ってきて指定すると消せるんだなというのはわかるが、UserDefaultsを消すときにBundle Identifierが必要なのかとか色々考えてしまったのでUserDefaultsについて調べてみました。
バージョン
Xcode: v11.3
Swift: v.5.3.2
iOS: v14.4
対象読者
-
iOSアプリ開発初心者
-
UserDefaultsをなんとなく使っている人
内容
UserDefaultsとは
UserDefaultsはユーザーごとの設定など保存するために提供されたKey-Valueストアです。また、UserDefaultsに保存された値は、削除しない限り永続的に保存され続けます。
ご存知ではあると思いますが、ユーザー個人のデータを保存する簡易的なデータベースみたいな感じですね。
Bundle IdentifierとUserDefaultsの関係は?
本題です。
結論から言うと、UserDefaultsを介して保存されるアプリ毎の設定を格納するファイル名にBundle Identifierが使われるという関係です。つまり、UserDefaultsはデータの保存領域で、Bundle Identifierはその領域の識別子として使われるという関係です。
つまりどういう事?
ここからはコードを使って説明します。
UserDefaultsは初めて書き込みが行われた時にアプリのRoot/Library/Preferences
に Bundle Identifier名.plist
というファイルをデバイス内に生成します。
まず、アプリのRoot/Library/Preferences
にBundle Identifier名.plist
が生成されていないことを確認しましょう。
Library/Preferences
までのパスはFileManager
を使うことで取得できます。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getFileNamesFromPreferences() // 何も出力されない
}
func getFileNamesFromPreferences() {
// Libraryまでのファイルパスを取得
let filePath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
// filePathにPreferencesを追加
let preferences = filePath.appendingPathComponent("Preferences")
// Library/Preferences内のファイルのパスを取得
guard let fileNames = try? FileManager.default.contentsOfDirectory(at: preferences, includingPropertiesForKeys: nil) else {
return
}
// Library/Preferences内のファイル名を出力
fileNames.compactMap { fileName in
print(fileName.lastPathComponent)
}
}
}
Bundle Identifier名.plistを生成してみる
UserDefaultsに値を保存することでBundle Identifier名.plistが生成されるか実際に確認してみましょう。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getFileNamesFromPreferences() // 何も出力されない
+ UserDefaults.standard.set("Value1", forKey: "test") // UserDefaultsに値を保存
+ getFileNamesFromPreferences() // com.test.UserDefaults.plist
}
func getFileNamesFromPreferences() {
// Libraryまでのファイルパスを取得
let filePath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
// filePathにPreferencesを追加
let preferences = filePath.appendingPathComponent("Preferences")
// Library/Preferences内のファイルのパスを取得
guard let fileNames = try? FileManager.default.contentsOfDirectory(at: preferences, includingPropertiesForKeys: nil) else {
return
}
// Library/Preferences内のファイル名を出力
fileNames.compactMap { fileName in
print(fileName.lastPathComponent)
}
}
}
お手元で実行してみるとプロジェクトのBundle Identifier名.plist
が保存されていることがわかると思います。
ちなみにpreferences
の値を使ってターミナルからも確認できます。
removePersistentDomain(forName:)
UserDefaultsに書き込みを行った際に生成される.plistファイルはBundle Identifier名でドメイン(簡単に言うと領域, 区画みたいなやつ)として認識されるみたいで、ドメイン名を指定することでドメイン内の値をすべて消すことができるみたいです。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getFileNamesFromPreferences() // 何も出力されない
UserDefaults.standard.set("Value1", forKey: "test") // UserDefaultsに値を保存
getFileNamesFromPreferences() // com.test.UserDefaults.plist
+ UserDefaults.standard.removePersistentDomain(forName: "com.test.UserDefaults.plist") // ドメイン内の値を全て削除
+ getFileNamesFromPreferences() // com.test.UserDefaults.plist
}
func getFileNamesFromPreferences() {
// Libraryまでのファイルパスを取得
let filePath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
// filePathにPreferencesを追加
let preferences = filePath.appendingPathComponent("Preferences")
// Library/Preferences内のファイルのパスを取得
guard let fileNames = try? FileManager.default.contentsOfDirectory(at: preferences, includingPropertiesForKeys: nil) else {
return
}
// Library/Preferences内のファイル名を出力
fileNames.compactMap { fileName in
print(fileName.lastPathComponent)
}
}
}
実行結果からわかるように、.plistファイル自体は削除されないみたいです。
終わりに
今回はUserDefaultsの内部構造に踏み込んでみました。
iOSの勉強は初めてからずっと心にあったモヤモヤが解消されてよかったです。
みなさんの参考になれば幸いです。
参考文献
swift - アプリ内で保存したファイルの一覧を取得したい - スタック・オーバーフロー
Swift5でDocumentディレクトリのファイルにアクセスする - Qiita
【Swift】URLでパスを扱うときに便利なプロパティとメソッド - Qiita
UserDefaults under the hood 💁🏻 | by Aaina jain | Swift India | Medium