SwiftUIで開発していくにあたって @State
やら@Binding
が沢山でてきて最初困惑しました。
これらの@○○はProperty Wrapperと呼ばれるものです。
Swift5.1から導入された、言語レベルの機能です。
...ということなので、SwiftUIが提供しているわけではないのです。
SwiftUIは、あくまで言語機能であるProperty Wrapperを利用して、
UIのための便利なプロパティラッパー(@State
, @Binding
, @StateObject
, @EnvironmentObject
, @ObservedObject
etc...)を提供しているわけです。
UIKitでの開発時は、全く使用してなかったので私は勘違いしてました。苦笑
Property Wrapperとは
ドキュメントは以下の通り。
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Property-Wrappers
プロパティラッパーは、プロパティの保存方法を管理するコードと、プロパティを定義するコードとの間に、分離の層を追加します。
ちょっとわかりづらいので、具体例を。
温度を-50度〜50度の範囲に補正する処理があるとします。
普通に書いたら以下のようになります。
struct Temperature {
private var _celsius: Int = 0
init(celsius: Int) {
self.celsius = celsius
}
var celsius: Int {
get { _celsius } // setterで処理した値を取得
set { _celsius = min(max(newValue, -50), 50) } //ここで-50度〜50度範囲内に補正
}
}
var temp = Temperature(celsius: 100)
print(temp.celsius) // 50(範囲外なので上限に補正)
これをProperty Wrapperを使用して書いた場合こうなります。
@propertyWrapper
struct Clamped {
private var value: Int = 0
init(wrappedValue: Int) {
self.wrappedValue = wrappedValue
}
// wrappedValueは、ラップされたプロパティの値にアクセスするためのプロパティで必須
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, -50), 50) }
}
}
// ↑↑↑↑
// プロパティの保存方法を管理するコード
// -----分離-----
// ↓↓↓↓
// プロパティを定義するコード
struct Temperature {
@Clamped var celsius: Int // celsiusプロパティにClampedラッパーを適用
}
var temp = Temperature(celsius: 100)
print(temp.celsius) // 50
- プロパティの保存方法を管理するコード(温度を-50~50に制御して保管する処理)
- プロパティを定義するコード:(実際に使用する箇所での定義)
ということで、上記の2つが確かに分離されているというわけです。
メリット
-
コードの重複を防ぐ
同じロジックを複数のプロパティに適用したい場合、ラッパーを定義することで再利用できます。 -
メンテナンス性の向上
ロジックを1カ所で管理するため、修正や仕様変更が容易です。 -
意図が明確になる
適切な命名ができていれば、プロパティにどんな処理が適用されているかコードの意図が伝わりやすくなります。 -
カプセル化により、単純なアクセスになる
ロジックがラッパー内部に隠蔽されているため、内部の詳細な実装を意識せずに外部から単純なプロパティとして使用できます。
まとめ
Property Wrapperは共通の振る舞いや制御をカプセル化し、複数の場所で簡単に再利用できるようになるというものでした。
よってSwiftUIが提供する@State
などもこの仕組みを利用しているため、私たちは内部の詳細な実装を意識せずとも、@State
をつけるだけで恩恵が受けられるというわけですね。