やさしい Property Wrapper

この記事では Swift の Property Wrapper が何かをやさしく解説します。

## PropertyWrapper とは

公式ドキュメントには下記のように説明されています。

https://docs.swift.org/swift-book/LanguageGuide/Properties.html
> A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property.

> プロパティラッパーは、プロパティの保存方法を管理するコードと、プロパティを定義するコードとの間に、分離の層を追加します。

上記の説明を理解するために、試しにコードを書いて確認していきましょう！

`value` プロパティに値を保存する際、何か処理を実行する `A` struct があるとします。

```swift
struct A {
    // プロパティの保存方法を管理するコードと、プロパティを定義するコード
    private var _value: Bool = false // <- 管理
    var value: Bool { // <- 定義
        set { // <- 保存方法
            print("Do something")
            _value = newValue
        }
        get {
            _value
        }
    }
}
```

これを `propertyWrapper` を使うとこのように書けます。

```swift
// 分離の層
@propertyWrapper
struct DoSomething {
    private var _value = false // <- 管理
    var wrappedValue: Bool {
        get { _value }
        set { // <- 保存方法
            print("Do something")
            _value = newValue
        }
    }
}

struct A {
    @DoSomething var value: Bool // <- 定義
}
```

確かに、プロパティの定義と保存方法を分離できてますね！

## メリット

> For example, if you have properties that provide thread-safety checks or store their underlying data in a database, you have to write that code on every property. When you use a property wrapper, you write the management code once when you define the wrapper, and then reuse that management code by applying it to multiple properties.

> 例えば、スレッドセーフのチェックを行うプロパティや、基礎となるデータをデータベースに保存するプロパティがある場合、すべてのプロパティにそのコードを書かなければなりません。プロパティ・ラッパーを使用すると、ラッパーの定義時に管理コードを一度だけ記述し、その管理コードを複数のプロパティに適用して再利用することができます。


property wrapper を活用すると、管理コードを再利用することができるのがメリットのようです。

## projectedValue について

上記が propertyWrapper のメインとする機能なのですが、さらに追加機能として `projectedValue` があります。`projectedValue` は名前の通り `propertyWrapper` によってラップされた値の投影された値として使用することができます。

先程の `DoSomething` に `projectedValue` の機能を追加すると次のようになります。

```swift
@propertyWrapper
struct DoSomething {
    private var _value = false
    var projectedValue = { // <- ここを追加
        print("Do additional something")
    }
    var wrappedValue: Bool {
        get { _value }
        set {
            print("Do something")
            _value = newValue
        }
    }
}
```

定義したプロパティ名の前に `$` を付けて参照します。

```swift
var a = A()
print(a.$value()) // Do additional something
```

## イニシャライザ

`DoSomething` の `value` property の初期値は `false` ですが、イニシャライザを定義することで、変数の定義時に初期値を設定することができます。

```swift
struct DoSomething {
    init(wrappedValue: Bool) {
        self._value = wrappedValue
    }
    ...
}

```
```swift
struct A {
    @DoSomething var value = true
}
```

## 使い方の例

さて、Property Wrapper が何か少しわかってきたところで、実際にどのように利用できるのか見ていきましょう。
ここでは、proposal に記載されている例の一部を紹介します。
`Userdefaults` へのアクセスをラップしたものです。

https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#user-defaults

```swift
@propertyWrapper
struct UserDefault<T> {
  let key: Key
  let defaultValue: T

  var wrappedValue: T {
    get {
        return UserDefaults.standard.object(forKey: key.rawValue) as? T ?? defaultValue
    }
    set {
        UserDefaults.standard.set(newValue, forKey: key.rawValue)
    }
  }
}

extension UserDefault {
    enum Key: String {
        case isFooFeatureEnabled
    }
}

Struct A {
    @UserDefault(key: .isFooFeatureEnabled, defaultValue: false)
    static var isFooFeatureEnabled: Bool
}
```

このような使い方をすることで、ユーザーデフォルトへのアクセスをスッキリと書くことができて良さそうですね！
proposal には他の例も書かれていますし、他にも応用できそうなものはないか考えてみると面白そうです！

## State を自前実装してみる

最後に、SwiftUI の @State を自前実装したらどうなるか考察してみました。

```swift
import SwiftUI
import Combine

class Store<T> {
    var value: T

    init(value: T) {
        self.value = value
    }
}

@propertyWrapper
struct State2<T> {

    private let store: Store<T>
    var viewGraph: ViewGraph? // <- フレームワークによって注入される

    init(wrappedValue value: T) {
        self.value = value
        self.store = Store(value: value)
    }

    private var value: T

    var projectedValue: Binding<T> {
        Binding {
            print("get \(store.value)")
            return store.value
        } set: { newValue in
            store.value = newValue
            print("set \(store.value)")

            viewGraph.render() // <- body を呼ぶ
        }
    }

    var wrappedValue: T {
        nonmutating set {
            store.value = newValue
        }
        get {
            store.value
        }
    }
}

struct PlayerView: View {
//    @State private var isPlaying: Bool = false
    @State2 private var isPlaying: Bool = false

    var body: some View {
        PlayButton(isPlaying: $isPlaying)
    }
}

struct PlayButton: View {
    @Binding var isPlaying: Bool

    var body: some View {
        Button(action: {
            self.isPlaying.toggle()
        }) {
            Image(systemName: isPlaying ? "pause.circle" : "play.circle")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        PlayerView()
    }
}
```

結果として`View` の `body` が呼ばれる仕組みが分からなかったので、実際にUIを動かしてみることはできませんでしたが、`body` が呼ばれる仕組みについてはこちらの[記事](https://kateinoigakukun.hatenablog.com/entry/2019/06/08/232142)に詳しく考察と解説をされていました。

`Field Descriptor` を使ったリフレクションで、`View` の `State` プロパティに `ViewGraph` というオブジェクトを注入し、値が更新されたタイミングで `ViewGraph` を使って更新させているようです。勉強になりますね！

今回は以上です。ではでは 👋

---
参考リンク
https://docs.swift.org/swift-book/LanguageGuide/Properties.html
https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#introduction
https://kateinoigakukun.hatenablog.com/entry/2019/06/08/232142