Swift5.1(?)の新機能Property Wrappersを学ぶために、Property Wrappersを使った値の検査を書いてみました。
まず検査を担当する、Wrapperすると言ったら良いのかな?の型です。
@propertyWrapper
struct Condition<T> {
let inCond: (T) -> Bool
let outCond: (T) -> Bool
var _value: T!
init(in inCond: @escaping (T) -> Bool, out outCond: @escaping (T) -> Bool) {
self.inCond = inCond
self.outCond = outCond
}
var value: T {
get {
precondition(outCond(_value))
return _value
}
set {
precondition(inCond(newValue))
_value = newValue
}
}
}
@propertyWrapperを付けて記述します。inCond・outCondは代入時・読み出し時の検査です。@propertyWrapperではvalueプロパティの宣言が必要です。
次にConditionを利用するコードを書いてみます。
class A {
@Condition(in: { !$0.string.isEmpty }, out: { $0.string == $0.string2 })
var b: B
func copy() {
$b._value.copy()
}
}
struct B {
var string: String
var string2: String = ""
mutating func copy() {
string2 = string
}
}
var a = A()
a.b = B(string: "test")
a.copy()
print(a.b)
Aには@Conditionを付けて宣言したvar b:Bがあります。このプロパティbがConditionでラップされていることになります。
a.b = B(string: "test")
で代入するときに@Conditionのinが実行されてpreconditionに渡されます。
print(a.b)
で読み出すときにoutが実行されます。
もしB(string: "")
(空文字列)を代入するとinがfalseになってpreconditionでクラッシュします。a.copy()を行ってB.stringをB.string2に代入しておかないとoutがfalseになってクラッシュします。
func copy()
では、$b._value.copy()を実行しています。もしb.copy()を行うと@Conditionの読み出しが発生してoutがfalseになるのでクラッシュしてしまいます。ですので、@Conditionを回避するにはCondition._valueを直接操作する必要がありました。
これでだいたいの動作が把握できたように思います。
この検査、ちょっとDbCっぽい記述かなと思いました。クロージャをラップしてDbCの様な記述ができると面白いと思いましたが、
@Condition(...)
var f: (String) -> String
のfの型のConditionでの処理が思いつきませんでした。