【Swift】プロパティラッパーとは

# この投稿は...

SwiftUIフレームワークでアプリを開発するにあたって、必須のAPIであるプロパティラッパーが「どのような仕組みで動作しているのか」を、シンプルな例を挙げて解説します。

> Swiftを基礎から学ぶには
自著、工学社より発売中の「[まるごと分かるSwiftプログラミング](https://amzn.asia/d/7YPWJwl)」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。
> また、Swiftプログラミングを基礎から動画で学びたい方には、Udemyコース「[今日からはじめるプログラミング](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwii1qTuj_2BAxV4hlYBHbj5BqEQFnoECBYQAQ&url=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Fstart-programming-today%2F&usg=AOvVaw2vs4Sccq-bpfA4ZD2rCyNm&opi=89978449)」をお勧めします。

### 実行環境

Swift 5.9
Xcode 15
macOS Sonoma 14.3.1

# プロパティラッパーが解決できること

以下に定義するのは、「短い直線」をモデル化する`ShortLine`型です。

```swift:短い直線をモデル化する構造体
struct ShortlLine {
    private var _length = 0    // 外部からは見えない「隠しプロパティ」
    var length: Int {
        get { return _length }
        set { _length = min(newValue, 10) }
    }   
}
```

インスタンスの長さを保持するのは「隠しプロパティ」ですが、プログラマーは計算プロパティを介して「長さの値」にアクセスします。
といっても、プログラマーは隠しプロパティが存在することも、アクセスしているのが計算プロパティであることも意識する必要はありません。

実際、インスタンスの長さに「`10`より大きい数」を設定すると、自動的に値が調整されて`10`になります。

```swift:短い直線を10より大きい値にはできない
var line = ShortlLine()
line.length = 9
line.length    // 9

line.length = 15
line.length    // 10
```

今度は、似たようなオブジェクトとして「軽い重量」をモデル化する場合を考えます。
例えば、軽い重量をモデル化する構造体は、以下のように定義できます。

```swift:軽い重量をモデル化する
struct RightWeight {
    private var _weight = 0
    var weight: Int {
        get { return _weight }
        set { _weight = min(newValue, 10) }
    }   
}
```

やはり、インスタンスの重さに「`10`より大きい数」を設定すると、`10`に調整してくれます。

```swift:軽い重量を10より大きい値にはできない
var mass = rightWeight()
mass.weight = 12
mass.weight    // 10
```

「短い直線」も「軽い重量」も、本質的には「数値を10以下に調整する」ことをモデル化しています。
これは、以下のような構造体として定義できます。

```swift:数値を10以下に調整することをモデル化
struct TenOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 10) }
    }
}
```

「短い直線」と「軽い重量」の定義は`TenOrLess`型を利用することによって、以下のように実装を簡略化できます。

```swift:TenOrLess型を利用した
// 短い直線
struct ShortlLine {
    private var _length = TenOrLess()
    var length: Int {
        get { return _length.wrappedValue }
        set { _length.wrappedValue = newValue }
    }   
}

// 軽い重量
struct RightWeight {
    private var _weight = TenOrLess()
    var weight: Int {
        get { return _weight.wappedValue }
        set { _weight.wappedValue = newValue }
    }   
}
```

`TenOrLess`型を定義したことによって、「値を`10`以下に調整する」ためのコードを再利用できました。

# プロパティラッパーの利点

上の例で示したコードは、プロパティラッパーを使用すると限りなくシンプルに記述できます。

プロパティラッパーを利用するには、プロパティの本質をモデル化する型に`@propertyWrapper`属性をマークします。
こうすることで、`TenOrLess`プロパティラッパーを利用できるようになります。

```swift:
@propertyWrapper 
struct TenOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 10) }
    }
}
```

`TenOrLess`プロパティラッパーを利用すると、「短い直線」と「軽い重量」は以下のように定義できます。

```swift
// 短い直線
struct ShortlLine {
    @TenOrLess var length: Int
}

// 軽い重量
struct RightWeight {
    @TenOrLess var weight: Int
}
```

プロパティに`@TenOrLess`キーワードをマークするだけで、以前と同じ機能を実装できます。

```swift:動作確認
var newLine = ShortlLine()
newLine.length = 12
newLine.length    // 10
```

このように、プロパティラッパーは実装を抽象化して、難しいことを意識せずに機能を利用できるようにします。

SwiftUIフレームワークでは`@State`や`@Binding`、`@Bindable`などのプロパティラッパーを利用せずに実際的なアプリケーションを開発することはできません。
といっても、プロパティラッパーの利点は「実装を意識することなく、恩恵を受けられる」ことです。
それらのプロパティラッパーがどのように実装されて、背後でどのように機能しているかを理解する必要はありません。