【Swift】プロパティラッパ(Property Wrapper)で UserDefaults へのアクセスを簡単にする【Xcode】

iOS エンジニアなら一度は使ったことがあるであろう UserDefaults。
今まで私は以下のようなクラスを作って、UserDefaultsからデータを取り出したり格納したりしていました。
//
// 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/
//
/// 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 に値を格納するためのコードは以下のような感じです。
//
// 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









