ObservableObjectをProtocolで差し替える

# 概要
SwiftUIでのアプリ開発では、多くの場合はObservableObjectプロトコルを組み合わせるかと思います。
ObservableObjectプロトコル対応させたclassを作成し、ObservedObjectに設定して使います。

以下はTopContentViewModelをObservableObjectとして作成しています。

```swift
struct ContentView: View {
    @ObservedObject var viewModel: TopContentViewModel

    var body: some View {
        Button {
            viewModel.update()
        } label: {
            Text(viewModel.name)
        }
    }
}

@MainActor
class TopContentViewModel: ObservableObject {
    @Published var name: String = Date.now.description

    func update() {
        Task {
            let (data, _) = try await URLSession.shared.data(
                from: .init(string: "https://httpbin.org/get")!)
            name = String(data: data, encoding: .utf8) ?? "nil"
        }
    }
}
```

しかし、現在のTopContentViewModelは通信を伴うものになっていますが、プレビューではわざわざ通信させたくない、ダミーのViewModelに差し替えて通信をさせないようにしたい、という考えもあるかと思います。

WWDCの動画に[Structure your app for SwiftUI previews](https://developer.apple.com/videos/play/wwdc2020/10149/)という動画がありますので、そこの内容を参考にObservedObjectを差し替えられるようにします。

## ObservableObjectを継承したprotocolを作成する
ObservableObjectを継承したprotocolを作成します。ここでは`ContentViewModel`とします。
`@Published`として設定しているプロパティは`{ get set }`としておきます。

```swift
@MainActor
protocol ContentViewModel: ObservableObject {
    var name: String { get set }

    func update()
}
```


## ObservableObjectを継承したprotocolを使ってclassを作成する
そして、各種ObservableObjectはContentViewModelに変更します。
アプリ用に使うTopContentViewModelとは別にプレビュー用の`PreviewContentViewModel`を作成しました。

```swift
@MainActor
class TopContentViewModel: ContentViewModel {
    @Published var name: String = Date.now.description

    func update() {
        Task {
            let (data, _) = try await URLSession.shared.data(
                from: .init(string: "https://httpbin.org/get")!)
            name = String(data: data, encoding: .utf8) ?? "nil"
        }
    }
}

private class PreviewContentViewModel: ContentViewModel {
    @Published var name: String = Date.now.description

    func update() {
        name = Date.now.description
    }
}
```

## protocolを使ったViewを定義する
先ほど作ったprotocolを使うようにViewを変更します。
ジェネリクスを設定し、ObservedObjectも変更します。

```swift
struct ContentView<ViewModel: ContentViewModel>: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Button {
            viewModel.update()
        } label: {
            Text(viewModel.name)
        }
    }
}
```

## Viewの呼び出し

ObservedObjectでは、Viewの引数にContentViewModelプロトコルを継承したViewModelを追加することで差し替えが可能です。
これで、アプリとしての動作は通信を行い、プレビュー時は通信を行わない実装になりました。

```swift:app            
@main
struct ProtocolObservableApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(viewModel: TopContentViewModel())
        }
    }
}
```

```swift:preview            
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: PreviewContentViewModel())
    }
}
```

### EnvironmentObjectの場合

EnvironmentObjectの場合も同じように差し替えることが可能です。

```swift:view         
struct ContentView<ViewModel: ContentViewModel>: View {
    @EnvironmentObject var viewModel: ViewModel

    var body: some View {
        Button {
            viewModel.update()
        } label: {
            Text(viewModel.name)
        }
    }
}
```

```swift:preview            
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView<PreviewContentViewModel>()
            .environmentObject(PreviewContentViewModel())
    }
}
```