【Swift】Property Wrapper概要・使い方

iOS, Swift

この記事は最終更新日から1年以上が経過しています。

はじめに

こんにちは。iOSアプリ開発グループの神山です。

今更ながらSwiftUIについて勉強し始めたのですが、@Stateや@Bindingなどの@がついたプロパティを見かけることがあります。

これはProperty Wrapperという仕組みを使用していることを表すものなのですが、私自身は今まで使用する機会があまりありませんでした。

ということで、SwiftUIの理解を深めるためにもProperty Wrapperが実際にどのような処理をしているのかをまとめてみました。

Property Wrapperとは?

THE SWIFT PROGRAMMING LANGUAGEにはこのような記載があります。

A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property.

「Property Wrapperはプロパティの保存方法を管理するコードと、プロパティを定義するコードの間に分離のレイヤーを追加する」とのことです。

これはプロパティの値の保存方法や計算方法をProperty Wrapperとして定義し、同じような振る舞いを行うプロパティをProperty Wrapperにラップすることで同じような処理を書くことなく再利用できるようになる仕組みとも言えます。

例として、格納される値が12以下の値の場合は12が格納され、12より大きい場合はその値が格納されるProperty Wrapperを見てみましょう。

Property Wrapperを定義するためにはいくつかのルールがあります。
1. @propertyWrapperのattributeを記載
2. enum、struct、classで定義可能
3. wrappedValueの定義が必要(プロパティの振る舞いを記載)

実際に使用する場合は以下のようになります。

Property Wrapperとしての@TwelveOrLessではなく、単純なTwelveOrLess構造体として明示的にラップした場合は以下のように表せます。

比較して分かるように、Property WrapperはこのようにwrappedValueに対しての処理を簡潔に記載することができます。

初期値を持ったProperty Wrapper

先ほど説明した@TwelveOrLessにはnumberに初期値の0が与えられているため、@TwelveOrLessで定義されたSmallRectangleのwidthやheightのプロパティにはどちらも0が初期値として設定されます。

ただ、widthとheightにそれぞれ違う値の初期値を設定したい場合もあるかと思います。
その場合は、@TwelveOrLessのProperty Wrapperにイニシャライザを作成することで実現することができます。

このように初期値においてもイニシャライザを作成することで、汎用性の高いProperty Wrapperを定義することができます。

ProjectedValue(投影値)

Property Wrapperではプロパティの振る舞いを定義したwrappedValueだけでなく、それを投影したprojectedValueを定義することもできます。投影値にアクセスする場合は$記号が必要になります。

以下は12を超えた値を設定した場合はtrueを返し、それ以外はfalseを返すprojectedValueを定義しています。

具体的な使用例(UserDefaults)

アプリ内でUserDefaultsを使用する機会は多いかと思いますが、Property Wrapperを使用すると簡潔にまとめることができます。

ここではProperty Wrapperを使わない場合、Property Wrapperを使った場合、デフォルト値を持ったProperty Wrapperを使った場合の3つに分けて見てみましょう。

Property Wrapperを使わない場合

そこまで多くないのであれば問題ないかもしれませんが、保存する値が増える度にコードが増えてしまって非常に見にくくなりますし、同じような処理をそれぞれのプロパティで書くことになってしまいます。

Property Wrapperを使った場合

Property Wrapperに処理をまとめたことでUserDefaultsStorage内の定義がとてもシンプルになりました。しかし、wrappedValueで取得する際に処理をまとめたことでオプショナル型で取得するようになっていしまい、使用する際にnilかどうかの確認をする必要が出てきてしまいました。

デフォルト値を持ったProperty Wrapper使った場合

Property Wrapperにデフォルトの値を設定できるようにしたことで、オプショナル型にする必要なく値を取得できるようになりました。これで使用する際にnilのチェックをする必要も無くなりましたね。

おまけ

SwiftUIではUserDefaultsに対する操作に対して@AppStorageというProperty Wrapperが既に用意されており、上記で説明してきたものを行ってくれます。

enumも定義することができるので、SwiftUIの場合はわざわざ自分で定義する必要のない@AppStorageを使用するのが良いですね。

UserDefaultsのテスト

UserDefaultsのテストはアプリ本体にも影響が出る可能性があるため注意が必要です。

上記で説明したAppStorageでは任意のUserDefaultsを設定することができ、任意のUserDefaultsを設定することでアプリ本体に影響を出すことなくテストをすることができます。先ほど作成したUserDefaultsWrapperについてもテストしやすくするために、任意のUserDefaultsを設定できるようにしてテストコードを作成してみましょう。

外側からUserDefaultsを設定できるようにすることでアプリ本体に影響を出すことなくテストを作成することができました。

さいごに

Property Wrapperはなかなか自分で定義する機会は少ないので、はじめはとっつきにくいものかもしれませんが、プロパティの振る舞いをラップして再利用できる点など、使いこなせれば便利な機能であることが分かりました。

SwiftUIでは必然的に@Stateや@BindingといったProperty Wrapperを使用することになるかと思いますので、少しでもこの記事が理解の助けとなれば幸いです。

最後まで記事を読んで頂き、ありがとうございました。

参考文献

THE SWIFT PROGRAMMING LANGUAGE

【Swift】【SwiftUI】Property wrappers に入門してみた

iOS, Swift