【Swift】プロパティラッパ(Property Wrapper)で UserDefaults へのアクセスを簡単にする【Xcode】
iOS エンジニアなら一度は使ったことがあるであろう UserDefaults。
今まで私は以下のようなクラスを作って、UserDefaultsからデータを取り出したり格納したりしていました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// // UserDefaults へのアクセスをラップするクラス class MyUserDefaults{ // Bool 型の値をUserDefaultsで管理する static var sampleFlag: Bool{ get{ return UserDefaults.standard.object(forKey: "sampleFlag") as? Bool ?? false }set{ UserDefaults.standard.set(newValue, forKey: "sampleFlag") } } // String 型の値をUserDefaultsで管理する static var sampleData: String{ get{ return UserDefaults.standard.object(forKey: "sampleData") as? String ?? "" }set{ UserDefaults.standard.set(newValue, forKey: "sampleData") } } } |
こんなふうに、UserDefaults で管理したいデータごとにプロパティを作成していました。
このプロパティは計算型プロパティ(Computed property)として、ゲッターとセッター内で UserDefaults に対する操作を行っています。
ですがこのやり方だと、プロパティが増えるに伴って、同じような「get」「set」という構文が並ぶことになり、非常に冗長になってしまいます。いわゆるボイラープレートですね。
Swift 5.1 から導入された Property Wrapper を使うことで、もう少しスマートに実装することができるようになります。
本記事では、私が最近使用している UserDefaults のラッパーを備忘録として残しておきます。
Property Wrapper とは
Property Wrapper を簡単に説明すると、以下のような感じになると思います。
「プロパティがどのように値を返却するか、どのように初期値を決定するか、値がnilだった場合のデフォルト値はどうするか?といったプロパティの性質をあらかじめ定義しておき、その定義を任意のプロパティ に適用することができる」
Property Wrapper の詳細は以下の Proposal に載っています。
https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md
Property Wrapper を使用して UserDefaults へのアクセスをラップする
基本的には以下のサイトをベースにさせていただいています。
https://swiftsenpai.com/swift/create-the-perfect-userdefaults-wrapper-using-property-wrapper/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// /// UserDefaults へのアクセスをラップするための PropertyWrapper 定義 @propertyWrapper struct MyUserDefaults { private let key: String private let defaultValue: T private let userdef: UserDefaults init(key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue userdef = UserDefaults.standard } var wrappedValue: T { get { // UserDefaults にデータがなければデフォルト値を返す guard let data = userdef.object(forKey: key) as? Data else { return defaultValue } // データを取得 let value = try? JSONDecoder().decode(T.self, from: data) return value ?? defaultValue } set { let data = try? JSONEncoder().encode(newValue) userdef.set(data, forKey: key) } } } /// UserDefaults に格納するプロパティを定義する構造体 struct AppData { /// 初回起動フラグ @MyUserDefaults(key: "FirstLaunchFlag", defaultValue: true) static var firstLaunchFlag: Bool /// Bool や Int などの基本型以外も格納できる(Codable に準拠している場合のみ) @MyUserDefaults(key: "hoge", defaultValue: nil) static var hoge: Hoge? } |
実際に UserDefaults に値を格納するためのコードは以下のような感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// // Bool型のプロパティを格納 AppData.firstLaunchFlag = false /// Codableに準拠していれば構造体も格納できる struct Hoge: Codable{ var flag = false var data = "data" init(flag: Bool, data: String){ self.flag = flag self.data = data } } let hoge = Hoge(flag: false, data: "data") AppData.hoge = hoge |