Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
20
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Swift5.1 PropetyWrappersを使ったUserDefaultsの例

はじめに

Swift 5.1から新しく追加されたPropertyWrapper
@State@Bindingなどでお馴染みですが、カスタム属性を作成して使っているでしょうか?

今回は、WWDC2019セッションUserDefaultsを使用したカスタム属性の内容が紹介されていたので、その例を備忘録兼ねて書こうと思います。

PropertyWrappersとは?

Swift5.1で導入されている機能で、プロパティをラップしてアクセスパターンを指定することができる機能。
すごくざっくり言うと、「プロパティにアクセスする際の面倒な処理をまとめておいて、使う時に@なんちゃらで呼び出すことができるよ」というものです。

ちなみに@State@PublishedなどもPropertyWrappersです。

独自に定義する場合は@propertyWrapperを宣言して使います。

今まで

通常はこんな処理はしないと思いますが、例えばこのような処理があったとします。

struct Person {
    var name: String {
        get {
            UserDefaults.standard.object(forKey: "person_name") as? String ?? ""
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "person_name")
        }
    }
}

Person以外にも、このようなコンピューテッドプロパティを使用してUserDefaultsにアクセスするような処理が複数あったらとても冗長です。

PropertyWrappersを使う

ここでPropertyWrappersの出番です。
PropertyWrappersを使用してUserDefaultsのアクセスパターンを定義してみます。
(ちなみにこのUserDefaultsのアクセスパターンは冒頭で書いたように、WWDC2019セッションで紹介されている例です。)

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

こう定義することで、外部からUserDefaultでプロパティを宣言することが可能になりました。

今回の例だと、実際に@UserDefaultで定義されたプロパティにアクセスした際はこのwrappedValueの中が呼ばれます。

@UserDefaultを使ってみる

実際に使ってみます。

先ほどPersonクラスで定義していた処理がこんなにすっきり書くことができました。

struct Person {
    @UserDefault("person_name", defaultValue: "")
    var name: String
}

通常通り以下のように使用できます。

let a = Person()
a.name = "Taro"

ただ、nameプロパティも同時に初期化する場合、このままでは型がUserDefault<String>となっているので、明示的にイニシャライザを追加してあげる必要があります。

struct Person {
    @UserDefault("person_name", defaultValue: "")
    var name: String

    init(name: String) {
        self.name = name
    }
}

これで引数付きで初期化できるようになりました。

let a = Person(name: "Taro")

上の例は実用的でないので、実際に使う場合は以下のようにStaticで宣言して使う感じになるかと思います。

struct UserDefaultsConfig {
    @UserDefault("isLogin", defaultValue: false)
    static var isLogin: Bool

    @UserDefault("username", defaultValue: "")
    static var username: String

    @UserDefault("age", defaultValue: 1990)
    static var age: Int
}

すっきり。

    UserDefaultsConfig.isLogin = true
    UserDefaultsConfig.username = "Yamada Taro"
    UserDefaultsConfig.age = 24

さいごに

どうでしょう。思ったより簡単でした。
PropertyWrappersを使用することで、プロパティのアクセスパターンを繰り返し書く必要がなくなります。
そして宣言的にカスタム属性を足すだけで、その定義をプロパティに当てはめられるようになるので、よりシンプルに、完結的な記述をすることができるようになりますね。

良かったらみなさんも使ってみてください。

以上、UserDefaultsを使った例でしたが、まだまだ、理解できていないところもありますので、ご指摘やアドバイス等ありましたら、コメントしていただければと思います。

それではお読みいただきありがとうございました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
20
Help us understand the problem. What are the problem?