【SwiftUI】各種Property Wrapperの使いどころを整理してみた（State, Binding...）

# はじめに
SwiftUIでは`@State`や`@Binding`など様々なProperty Wrapperが用意されていますが、毎回何をいつどのように使うべきかを忘れてしまうので、備忘録としてまとめてみました。

# 本記事で取り上げるProperty Wrappers
* `@State`
* `@Binding`
* `@Bindable`
* `@Environment`
* `@StateObject`
* `@ObservedObject`
* `@EnvironmentObject`

# 最初にまとめ
## 値型の変数（String, Bool 等）
* 信頼できる情報源として宣言する場合 -> `@State`
* 信頼できる情報源が所有する変数を読み書きする場合 -> `@Binding`
* 環境変数を取得する場合 -> `@Environment`

## 参照型の変数（オブジェクト）
### `Observable`プロトコル（マクロ）に準拠したオブジェクト（2023〜）
* 信頼できる情報源として宣言する場合 -> `@State`
* 信頼できる情報源が所有するオブジェクトの<INS>プロパティ</INS>とバインディングする場合 -> `@Bindable`
* 信頼できる情報源が所有するオブジェクトの<INS>参照先</INS>とバインディングする場合 -> `@Binding`
* 環境変数として設定されたオブジェクトを取得する場合 -> `@Environment`

### `ObservableObject`プロトコルに準拠したオブジェクト（2019〜）
* 信頼できる情報源として宣言する場合 -> `@StateObject`
* 信頼できる情報源が所有するオブジェクトの`@Published`プロパティを追跡する場合 -> `@ObservedObject`
* 環境変数として設定されたオブジェクトの`@Published`プロパティを追跡する場合 -> `@EnvironmentObject`

# State
## 概要
ビューの中で状態管理（追跡）を行う変数に対して付与するラッパー。`@State`変数の値に変更が加えられると、その値を使用するビューも更新される。
`@State`を付与した変数の値はビュー自身がその所有者となり、「**信頼できる唯一の情報源**」（Single Source of Truth, SSOT）として機能する。

## コード例
```Swift
struct ToggleView: View {
    @State private var isOn = false

    var body: some View {
        Toggle("Sample Toggle", isOn: $isOn)
        if isOn {
            Text("Toggle is on.")
        }
    }
}
```

また、`@State`変数には、**`Observable`プロトコル（マクロ）に準拠したオブジェクト**も格納することができ、オブジェクト内のプロパティの変更も追跡してくれる。
```Swift
@Observable
class Book {
    var title = "Sample Book Title"
}

struct ContentView: View {
    @State private var book = Book()
    
    var body: some View {
        BookView(book: book)
    }
}

struct BookView: View {
    var book: Book
    
    var body: some View {
        Text(book.title)
    }
}
```
:::note warn
`Observable`プロトコル（マクロ）ではなく、`ObservableObject`プロトコルに準拠したオブジェクトも`@State`変数に格納できるが、その場合、その参照先が変わったときにしかビューが更新されない。つまり、**オブジェクト内のプロパティの更新は追跡されない**。
`ObservableObject`プロトコルに準拠したオブジェクトのプロパティの更新も追跡する場合は、後述する`@StateObject`を使用する。
:::


### コードの補足
* `@State`を付与した変数は`private`として宣言する。
    * SwiftUIが自動生成するinitializer (memberwise initializer)の中で変数が設定されることを防ぐため。
* `@State`変数の頭に`$`を付与することで、変数の値そのものではなく、バインディングを子ビューに渡すことができる。
    * `$`はProperty Wrapperにおける`projectedValue`を指す。Stateでは、projectedValueとして`Binding<Value>`型の変数が格納される（BindingやBindableも同様）。
* デフォルトの`Observable`オブジェクトを作成して`@State`変数に格納する場合、ビューが更新されるたびにオブジェクトが作成され、パフォーマンス上の無駄が発生する可能性がある。その場合は、以下のように`task`モディファイアの中でオブジェクトを作成することで、ビューが最初に表示される時にだけオブジェクトを作成できる。
```Swift
BookView(book: book)
    .task {
        book = Book()
    }
```

# Binding
## 概要
`@State`として宣言された（＝**信頼できる情報源によって所有されている**）変数を参照したり、更新したりする（＝バインディングする）ためのラッパー。
主に親ビューが持つ`@State`の値を、子ビュー側で読み書きしたいときに使用する。

## コード例
```Swift
struct ParentView: View {
    @State private var text = ""
    var body: some View {
        ChildView(text: $text) // バインディング（projectedValue）を渡す
    }
}

struct ChildView: View {
    @Binding var text: String
    var body: some View {
        TextField("Sample Field", text: $text)
    }
}
```

:::note warn
（信頼できる情報源から渡される）`Observable`マクロが適用されたオブジェクトとバインディングすることも可能だが、その場合は**参照先の変更しか追跡されない**。
`Observable`オブジェクトの**プロパティ**を追跡したい場合は、後述の`@Bindable`を使用する。
:::

# Bindable
## 概要
**`Observable`プロトコル（マクロ）に準拠したオブジェクト**のプロパティとバインディングするために使用するラッパー。（※オブジェクト自体は信頼できる情報源が所有）
2023年の`Observable`マクロの登場に合わせて追加されたラッパーで、**iOSの場合、17.0以降で使用可能**。
※参考：[SwiftUIにおけるObservationの説明](https://developer.apple.com/jp/videos/play/wwdc2023/10149/)

## コード例
```Swift
@Observable
class Book {
    var title = "Sample Book Title"
}

struct ContentView: View {
    @State var book = Book()
    
    var body: some View {
        BookView(book: book)
    }
}

struct BookView: View {
    @Bindable var book: Book

    var body: some View {
        TextField("Title", text: $book.title)
    }
}
```

また、`@Bindable`はローカル変数に対しても付与することができる。
例えば、以下のようにList内でオブジェクトを1つずつ取り出し、`@Bindable`な変数として定義できる。
```Swift
@Observable
class Book: Identifiable {
    var title = "Sample Book Title"
    var isAvailable = true
}

struct LibraryView: View {
    @State private var books = [Book(), Book(), Book()]
    
    var body: some View {
        List(books) { book in
            @Bindable var book = book
            HStack {
                Text(book.title)
                Toggle("", isOn: $book.isAvailable)
            }
        }
    }
}
```

# Environment
## 概要
ビューの環境変数を読み取り、追跡するためのラッパー。`@Environment`変数の値に変更が加えられると、その値を使用するビューも更新される。

## コード例
```Swift
struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme: ColorScheme
    
    var body: some View {
        if colorScheme == .dark {
            Text("Dark Mode")
        } else {
            Text("Light Mode")
        }
    }
}
```

また、**`Observable`プロトコル（マクロ）に準拠したオブジェクト**を読み取ることもできる。その場合は、上位ビュー（または`App`）において`environment`モディファイアにオブジェクトを格納する。
```Swift
@Observable
class Book {
    var title = "Sample Book Title"
}

struct AppView: View {
    @State private var book = Book()
    
    var body: some View {
        BookView()
            .environment(book)
    }
}

struct BookView: View {
    @Environment(Book.self) var book: Book

    var body: some View {
        Text(book.title)
    }
}
```

# StateObject
## 概要
**`ObservableObject`プロトコルに準拠したオブジェクト**の状態管理を行うためのラッパー。`@Published`が付与されたプロパティの変更を追跡したり、読み書きしたりできる。
`@StateObject`を付与したオブジェクトはビュー自身がその所有者となり、「**信頼できる唯一の情報源**」（Single Source of Truth, SSOT）として機能する。

## コード例
```Swift
class Book: ObservableObject {
    @Published var title = "Sample Book Title"
    @Published var isAvailable = true
}

struct ContentView: View {
    @StateObject private var book = Book()
    
    var body: some View {
        HStack {
            Text(book.title)
            Toggle("", isOn: $book.isAvailable)
        }
    }
}
```
:::note warn
値型の変数、または`Obervable`プロトコル（マクロ）に準拠したオブジェクトに対しては、先述の`@State`を使用する。
:::

# ObservedObject
## 概要
**`ObservableObject`プロトコルに準拠したオブジェクト**をインプットとして受け取り、`@Published`プロパティの変更を追跡したり、読み書きしたりする際に使用するラッパー。（※オブジェクト自体は信頼できる情報源が所有）
主に親ビューが持つ`@StateObject`のプロパティを、子ビュー側で読み書きしたいときに使用する。

## コード例
```Swift
class Book: ObservableObject {
    @Published var title = "Sample Book Title"
    @Published var isAvailable = true
}

struct ContentView: View {
    @StateObject private var book = Book()
    
    var body: some View {
        BookView(book: book)
    }
}

struct BookView: View {
    @ObservedObject var book: Book

    var body: some View {
        HStack {
            Text(book.title)
            Toggle("", isOn: $book.isAvailable)
        }
    }
}
```

# EnvironmentObject
## 概要
上位ビューで`environmentObject`モディファイアにセットされたオブジェクトを追跡、読み書きするためのラッパー。
**`ObservableObject`プロトコル準拠のオブジェクト**に対して使用する。

## コード例
```Swift
class Book: ObservableObject {
    @Published var title = "Sample Book Title"
    @Published var isAvailable = true
}

struct ContentView: View {
    @StateObject private var book = Book()
    
    var body: some View {
        BookView()
            .environmentObject(book)
    }
}

struct BookView: View {
    @EnvironmentObject var book: Book

    var body: some View {
        HStack {
            Text(book.title)
            Toggle("", isOn: $book.isAvailable)
        }
    }
}
```
:::note warn
`Observable`プロトコル（マクロ）に準拠したオブジェクトに対しては、先述の`@Environment`を使用する。
:::

# おわりに
誤り等ありましたら、お手数ですがコメントにてご指摘いただけますと幸いです。

# 参考
* [Model data | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/model-data)
* [Environment values | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/environment-values)
* [Properties | Documentation | The Swift Programming Language (6.2)](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties)