UserDefaults を部分的に Codable でデコードする
Swift プログラミング
UserDefaults のデータの特定の部分を Codable を使って独自の型のインスタンスに読み込んだり、それを UserDefaults に書き戻す方法を調べてみました。
Swift では Codable
を使って、JSON データやプロパティーリストのデータと型とを相互変換できるようになっています。
iOS アプリや macOS アプリの設定を保存するのに使う UserDefaults
も内部でプロパティーリスト形式のデータを扱うものになっているので、プロパティーリストファイルに適合する型を作ってそのインスタンスを生成することができるのですが、UserDefaults
の使い勝手の良さもあり、さまざまな情報が自動で格納されていたりすることもあって、適合する型を維持管理していくのも少し厄介です。
ただ、プロパティーリストのデータは、特別な準備をしなくても、部分的に構成要素を取り出して、その要素とそれが内包する部分に限って Codable
による型との相互変換が行えるので、そのやり方を知っておくと便利かもしれません。
UserDefaults のデータ
たとえば、次のような UserDefaults
のデータがあったとします。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Target Applications</key>
<array>
<dict>
<key>Bundle Identifier</key>
<string>com.apple.mainstage3</string>
</dict>
<dict>
<key>Bundle Identifier</key>
<string>com.apple.FinalCut</string>
</dict>
</array>
<key>NSWindow Frame NSSpellCheckerSubstitutionsPanel2</key>
<string>692 407 425 137 0 0 2560 1415 </string>
</dict>
</plist>
今回はこのデータの元ファイルに当たるプロパティーリストファイル全体から型に変換するのではなく、ここから Target Applications
をキーにした配列のところだけを Codable
で変換できるようにしてみます。
適合する型の定義
今回は Target Applications
キーの内容に適合する型を作りますけれど、その値は配列になっていて、配列自体は標準で Codable
に対応しているため、今回はその中身に適合する型を定義します。
構造自体はとても簡単で、キーである "Bundle Identifier" に該当するプロパティーをひとつ、そのデータを格納するために String型 で用意します。
struct Application : Codable {
var bundleIdentifier: String
}
private extension Application {
enum CodingKeys : String, CodingKey {
case bundleIdentifier = "Bundle Identifier"
}
}
Codable による相互変換
ここまで準備ができたら、あとは UserDefaults
の Target Applications
の内容と Application型
の配列とを相互変換可能になっています。
UserDefaults の一部を独自の型として読み込む
まずは UserDefaults
に保存されている Target Applications
の内容から [Application]型
を生成してみます。
func getTargetApplications() throws -> [Application] {
let userDefaults = UserDefaults.standard
guard let object = userDefaults.object(forKey: "Target Applications") else {
fatalError()
}
let decoder = PropertyListDecoder()
let data = try PropertyListSerialization.data(fromPropertyList: object, format: .xml, options: 0)
return try decoder.decode([Applications].self, from: data)
}
処理の流れとしては、目的のキーに該当するデータをオブジェクトとして UserDefaults
から取り出し、それをいったん PropertyListSerialization
を使ってプロパティーリストの形に整えた上で、そのデータを Codable
に対応した PropertyListDecoder
を使って目的の型のインスタンスを生成しています。
独自のデータ型の値を UserDefaults に書き込む
書き込みも先ほどの処理と逆のような流れで作っていけます。
流れとしては、値を Codable
でエンコードしたら、それをオブジェクトに変換して UserDefaults
の目的のに書き込みます。
func setTargetApplications(_ applications: [Application]) throws {
let userDefaults = UserDefaults.standard
let encoder = PropertyListEncoder()
let data = try encoder.encode(applications)
let object = try PropertyListSerialization.propertyList(from: data, format: nil)
userDefaults.set(object, forKey: "Target Applications")
}
このようにすることで [Application]型
の値を UserDefaults
の Target Applications
に該当するところにプロパティーリストのデータとして書き戻すことができました。