Property Wrappers とは、簡単に言えば「プロパティに関する制御をテンプレ化できる仕組み」です。
厳密に言えば、「プロパティの定義と制御の間に1つレイヤーを挟むための仕組み」です。
Swift 5.1で実装されました。
Property Wrappersを使わないこれまでの書き方
例えば、学校のテスト結果(国語、数学、理科、社会、英語)を入れる構造体を定義したとして、点数を0〜100までの範囲に限定したい場合、以下のように書いてきました。
struct TestResult {
/// 国語
private var _japanese: Int = 0
var japanese: Int {
get { return _japanese }
set { _japanese = min(100, max(0, newValue)) }
}
/// 数学
private var _math: Int = 0
var math: Int {
get { return _math }
set { _math = min(100, max(0, newValue)) }
}
/// 理科
private var _science: Int = 0
var science: Int {
get { return _science }
set { _science = min(100, max(0, newValue)) }
}
/// 社会
private var _social: Int = 0
var social: Int {
get { return _social }
set { _social = min(100, max(0, newValue)) }
}
/// 英語
private var _english: Int = 0
var english: Int {
get { return _english }
set { _english = min(100, max(0, newValue)) }
}
}
各教科それぞれにゲッターとセッターを書いて、さらにはStored Propertyとしての変数も切ってと、とても面倒でした。
Property Wrappersを使った書き方
この面倒なプロパティ定義・制御を、Property Wrappersを使うことでテンプレ化することができます。
方法として、まずは @propertyWrapper を付けてstruct(またはclass, enum)を定義します↓
@propertyWrapper
struct Score {
private var value: Int
init() {
value = 0
}
var wrappedValue: Int {
get { return value }
set { value = min(100, max(0, newValue)) }
}
}
wrappedValue プロパティの定義は必須で、これがProperty Wrappersの肝となります。
これだけでテンプレの定義はできたので、先ほどのTestResultを書き換えてみます↓
struct TestResult {
/// 国語
@Score var japanese: Int
/// 数学
@Score var math: Int
/// 理科
@Score var science: Int
/// 社会
@Score var social: Int
/// 英語
@Score var english: Int
}
各プロパティの頭に @ 付きで定義したものを書くだけ。
実行してみても意図したとおりに動いています。
var result = TestResult()
result.japanese = -80
print(result.japanese) // -> 0
result.math = 120
print(result.math) // -> 100
result.science = 70
print(result.science) // -> 70
result.social = 90
print(result.social) // -> 90
result.english = 150
print(result.english) // -> 100
素晴らしい。
初期値を渡す
プロパティ宣言時に初期値を渡すこともできます。
例えば、各教科一律で0〜100点にするのではなく、教科ごとに範囲を設けたい場合、先程のScore構造体を以下のように変更します。
@propertyWrapper
struct Score {
private var value: Int
private var limit: CountableClosedRange<Int>
init() {
self.value = 0
self.limit = 0...100
}
init(limit: CountableClosedRange<Int>) {
self.value = 0
self.limit = limit
}
var wrappedValue: Int {
get { return value }
set { value = min(limit.upperBound, max(limit.lowerBound, newValue)) }
}
}
limitプロパティを追加し、それを渡す用のイニシャライザも追加しました。
これでTestResultも書き換えてみます↓
struct TestResult {
/// 国語
@Score(limit: (-50)...150) var japanese: Int
/// 数学
@Score(limit: 0...150) var math: Int
/// 理科
@Score(limit: 0...50) var science: Int
/// 社会
@Score(limit: 100...1000) var social: Int
/// 英語
@Score var english: Int
}
まあちょっとあり得ない範囲設定になっていますが。。
動かしてみても、
var result = TestResult()
result.japanese = -80
print(result.japanese) // -> -50
result.math = 120
print(result.math) // -> 120
result.science = 70
print(result.science) // -> 50
result.social = 90
print(result.social) // -> 100
result.english = 150
print(result.english) // -> 100
いい感じです。
参考リンク
・より詳しい情報はこちら
Swift.org:
https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617
・参考
Qiita「Swift5.1 PropetyWrappersを使ったUserDefaultsの例」:
https://qiita.com/kazy_dev/items/b0ef4775d3acb96c200d