Posted at

Property Wrappersで値の検査をしてProperty Wrappersを学んでみた

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での処理が思いつきませんでした。