もっともシンプルな SwiftUI - MVVM

#SwiftUI - MVVM が理解できます

「 MVVM って、よく聞くけど、なんかわかったようなわからないような。。」
「 流石にそろそろ SwiftUI いじっときたいけど、書く手順とかコードの配置とかややこしそう」
と思っているそこのあなた、 この記事を読めば、 SwiftUI - MVVM のデザインの骨子がわかります。

#MVVM とは？

**MVVM** はコードデザインのパターンで、
**Model と View を分離する**
ことに主眼をおいています。

**Model はアプリが何をするかの実質内容**
**View はアプリをユーザーにどのように提示するかの方法**

そして、両者の変更を 

**ViewModel が翻訳してつたえあう**

ことにより、
Model はアプリの実質内容をわかりやすく一意に保つことができ、 
View は Model の反映を遅滞なくユーザーに提示できます。

#MVVM は必須？

**SwiftUI をつかってアプリをつくるために、 MVVM のパターンは必須ではありません**が、
MVVM をもちいることにより、
「アプリの内容変更をドバッと View にわたしてしまえば、 View がよろしく表現してくれる」宣言型の手法がとれ、スムースにアプリがかけるようです。
（ちなみに、「内容の更新ごとに View に『あれやってこれやって』と言う」のは命名型）

#一番シンプルな例で理解しよう

とはいえ、
**「ViewModel ってけっきょくなにを書くねん」、「 SwiftUI って @ObservedObject とか @Published とかいろいろ新キャラがでてきてこわそう」**
、と感じるのが人情だと思うので（ぼくがそうなので）、
シンプルなケーススタディーで SwiftUI と MVVM の組み合わせを書いてみました。

**最低限の Model View ViewModel で MVVM のパターンをつくります。**

ケーススタディーは、タップすると犬 ⇄ 猫が切り替わるスイッチです。 

【画像：タップすると切り替わるシンプルなスイッチ】

![スクリーンショット 2021-07-15 14.18.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/fa8a1087-0ea7-5a7a-262d-e7f9e34fa61e.png)  タップ ⇄ ![スクリーンショット 2021-07-15 14.19.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/11edcfce-6954-5d55-ee0a-f6f5cabeb7a6.png)

（わかりやすいように背景緑色にしてます）


#シンプルな MVVM

###Model を書く

このサンプルの Model 、つまりこのアプリケーションの実質は、犬と猫の切り替えです。

```swift:Model.swift
import Foundation // Model は SwiftUI をインポートしない

struct Model {
    
    enum Pet:String { // ケースは犬か猫か
        case 🐶
        case 🐱
    }

    var pet: Pet = .🐶 // 初期値は犬
    
    mutating func switchPet() { // 犬と猫を切り替える関数
        if pet == .🐶 {
            pet = .🐱
        } else {
            pet = .🐶
        }
    }
    
}
```

**Model は SwiftUI をインポートしません。 UI から独立したアプリの実質だからです。**

このアプリは犬なのか、猫なのか、の切り替えが実質なので、
Model は、犬か猫かの pet という変数と、 犬と猫を切り替える switchPet という関数でできています。
（struct が自身を変更するには mutating func をもちいます）

**我々のアプリの Model はこれだけです。**

###View を書く

View は Model の pet を Text View にして表示します。
また、 Text View がタップされたら、 Model の pet をスイッチします。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    
    var body: some View {
        Text("ここに Model の pet を反映する")
            .padding()
            .onTapGesture {
                // ここで model の pet をスイッチする
        }
    }

}

```

我々の View は
ユーザーに対する Model 内容の表示と、
ユーザーのタップを受け入れる役割を持っています。

MVVM のパターンを無視すれば、
**ここで Model を View で直接保持することも可能です。**


```swift:ダイレクトにModelをもつContentView.swift
import SwiftUI

struct ContentView: View {
    @State var model = Model()
     // @State をつけることで、 View の状態に関する値を変更、即時反映できる

    var body: some View {
        Text(model.pet.rawValue)
            .padding()
            .onTapGesture {
                model.switchPet()
        }
    }

}
```
**もっといえば、pet 変数と切り替え関数を View で持つことも、もちろん可能です。**

```swift:ダイレクトにpetとswitchPetをもつContentView.swift
import SwiftUI

struct ContentView: View {

    enum Pet:String {
        case 🐶
        case 🐱
    }

    @State var pet: Pet = .🐶
     // @State をつけることで、 View の状態に関する値を変更、即時反映できる
    
    mutating func switchPet() {
        if pet == .🐶 {
            pet = .🐱
        } else {
            pet = .🐶
        }
    }

    var body: some View {
        Text(pet.rawValue)
            .padding()
            .onTapGesture {
                switchPet()
        }
    }

}
```

このペットという変数が View の一時的な状態をあらわすだけなら、これでいいのかもしれません。

**しかしそうすると、たとえば、いくつも View があったときに Model の状態を一意に保つのが大変になったりします。
それをやらないのが MVVM です。**

###ViewModel を書く

ViewModel の役割は、 View と Model のコミュニケーションの通訳です。
View からユーザーのタップがあったときに Model に伝え
Model の状態を View に伝えることです。

```swift:ViewModel.swift
import Foundation
import SwiftUI

class ViewModel {
    var model:Model = Model() // Model をもつ
    
    var pet: String { 
        return model.pet.rawValue // Model の pet を View が必要とする String にして返す
    }
    
    func switchPet() {
        model.switchPet() // Model の switchPet を呼ぶ
    }
}
```

###View から ViewModel にアクセスする

View から ViewModel にユーザーのタップを伝え、
ViewModel から Model の切り替え関数を呼び、
ViewModel から View は Model の pet の値の変更を取得します。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var viewModel = ViewModel()
    
    var body: some View {
        ZStack {
            Text(viewModel.pet)
                .padding()
                .onTapGesture {
                    viewModel.switchPet()
                }
        }
    }
}
```

これを動かしてみると、 **UI が変わりません**。

確認のために、 Model のスイッチペットにプリントを入れてみると、

```swift:Model.swift
    mutating func switchPet() {
        if pet == .🐶 {
            pet = .🐱
        } else {
            pet = .🐶
        }
        print(pet)
    }
```

>🐱
🐶
🐱
🐶

Model の pet は切り替わっていますが、 UI は更新されていません。
先ほどの情報遷移のフローで言うと、

View から ViewModel にユーザーのタップを伝え、（できた）
ViewModel から Model の切り替え関数を呼び、（できた）
**~~ViewModel から View は Model の pet の値の変更を取得します。~~（ここが届いていない）**

MVVM では、 ViewModel は全体に向けて Model の変更をパブリッシュ（公開）し、 View は自分の知りたい情報を任意に購読（subscribe）する、というかたちで Model の更新情報を取得します。

ここで、SwiftUI のプロパティ・ラッパーが登場します。

###ViewModel が変更を公開し、 View が購読する

```swift:ViewModel.swift
import Foundation
import SwiftUI

class ViewModel:ObservableObject { // @ObservableObject をつける
    @Published var model:Model = Model() // @Published をつける
    
    var pet: String {
        return model.pet.rawValue
    }
    
    func switchPet() {
        model.switchPet()
    }
}
```

@ObservableObject （観察可能なオブジェクト）を継承することで、 ViewModel は観察対象になることができ、アプリ全体（の観察意思がある対象）に向けて情報を発信できるようになります。

@Published をつけることで、 この Model に変更があったとき即座に、 ViewModel (@ObservableObject) は全体にパブリッシュ（公開）できます。

そして、 View 側でこの変更のパブリッシュを購読します。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel() // @ObservedObject をつける
    
    var body: some View {
        ZStack {
            Text(viewModel.pet)
                .padding()
                .onTapGesture {
                    viewModel.switchPet()
                }
        }
    }
}
```
@ObservedObject （観察されるオブジェクト）を viewModel 変数につけることで、 @ObservableObject である ViewModel の公開する変更があったときに、 View は即時に body var から関連する UI を変更できます。

これで、 「ViewModel から View は Model の pet の値を取得する」部分ができあがり、タップによって UI も更新されるようになりました。


【画像：タップで更新される犬猫】
![Jul-16-2021 10-45-42.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/460f4f46-bb20-f136-7522-908e7c705c7f.gif)


**これがもっともシンプルな MVVM です。**
もっと色々あるんでしょうが、とりあえずのパターンの基本要素は入っていると思います。

🐣
****
フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

[Twitter](https://twitter.com/JackdeS11)
[Medium](https://medium.com/@rockyshikoku)