Property Wrappers
概要
プロパティをどのように保持/管理するか?のコードと、プロパティの定義コードを分離するレイヤーを作ることができる。例えば、以下のようなユースケースで有用。
任意のプロパティについてスレッドセーフに扱える
任意のプロパティについて DB のデータを同期させたい
このような場合には大抵プロパティ管理のためのボイラープレートが必要になるが、それをまとめて記述しておくことができる。
作り方
code:swift
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { number }
set { number = min(newValue, we) }
}
}
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height) // 0
rectangle.height = 10
print(rectangle.height) // 10
rectangle.height = 24
print(rectangle.height) // 12
コンパイラは、property wrapper を内部的に以下のように解釈している。実際にインスタンスを保持しているのは _height や _width であり、height 及び width は computed property として定義される。
code:swift
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
初期値
初期値をカスタマイズしたい場合は、initializer を追加する。
code:swift
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
initializer は以下のように利用できる
code:swift
// init() が利用される
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
// プロパティに初期値が与えられると、init(wrappedValue:) が自動で利用される
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
// property wrapperに引数を与えると、対応する initializer が呼び出される
// このケースでは .init(wrappedValue:maximum:) が利用される
struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
struct MixedRectangle {
@SmallNumber var height: Int = 1
// 直に値を指定するとwrappedValueの指定を省略できる
// 実際には init(wrappedValue:maximum:) が利用される
@SmallNumber(maximum: 9) var width: Int = 2
}
Projected value
property wrapper は単に値をラップするだけでなく、projedcted value を追加することで追加の機能を実装できる。projected value には $ を先頭に付与してアクセスできる。
code:swift
@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber) // false
someStructure.someNumber = 55
print(someStructure.$someNumber) // true
参考