はじめに
私はプログラミング学習3ヶ月目、Swift学習1ヶ月程度の新人エンジニアです。
最近実務で「設計パターン」や「MVVM」を学習する機会があったので、MVVMパターンを理解する過程の実体験をもとに「使うとどんないいことがあるのか」をまとめました。
MVVMとは?
MVVM(Model - View - ViewModel)とは、「画面の見た目」と「裏側の処理」を綺麗に分離して開発するための設計パターンです。
それぞれのファイルに役割を与えることで、コードがごちゃごちゃになるのを防ぎます。
- View(画面): レイアウトやボタンの配置など、見た目の描画を担当する。
- ViewModel(状態とロジック): 画面に表示するデータの保持や、「ボタンが押された時の処理」を担当する。
- Model(データと通信): アプリで扱うデータや、そのデータに関する処理を担当する。(API通信やデータベース処理は、専用のServiceやRepositoryに分けることもある。)
そもそも、1つのファイルにすべて書くと何がいけないの?
設計を意識しない場合、Viewのファイルの中に「レイアウト」も「計算処理」も「API通信」もすべて書いてしまいがちです。
これをやってしまうと、1つのファイルが何百行〜何千行にも膨れ上がり、「見た目を変えたいだけなのに、関係ない処理コードまで読み解かないといけない」という状態になり、メンテナンス性が著しく低下します。
そのため、MVVMを使って「見た目」と「処理(ロジック)」の役割分担をする必要があります。
MVVMの強み
実際にコードを触ってみてわかったMVVMの強みを2点まとめたいと思います。
1. データバインディングで「画面を書き換える指示」が不要になる
MVVMと非常に相性が良いのが「データバインディング」という仕組みです(※SwiftUIでは標準で備わっています)。
データバインディングとは、「データ(状態)」と「View(見た目)」を紐付け、データが変わった瞬間に画面も自動で連動して切り替わる仕組みのことです。
データバインディングなし(手動更新の例)
データが変わるたびに、「どのUIパーツをどう書き換えるか」という指示コードを手動で書く必要があります。
※今回は「お気に入りボタン(wishButton)」を例にしています。
// データが変わるたびに、UIを手動で更新する必要がある
struct WishButtonView: View {
@State private var isWishListed = false
@State private var buttonTitle = "お気に入り登録" // データとは別に見た目の状態を持っている
@State private var buttonColor = Color.blue
var body: some View {
Button(buttonTitle) {
// タップされるたびに、手動で文字や色を書き換える処理が必要になる
isWishListed.toggle()
if isWishListed {
buttonTitle = "お気に入り解除"
buttonColor = .gray
} else {
buttonTitle = "お気に入り登録"
buttonColor = .blue
}
}
.background(buttonColor)
}
}
この書き方だと、データ(isWishListed)と見た目の変数(buttonTitle や buttonColor)が分離しているため、UIの更新処理を書き忘れると、状態と画面表示が一致しなくなる可能性があります。
⭕️ MVVM(データバインディングあり / SwiftUI例)
最初に「データと見た目の関係(ルール)」を宣言しておけば、手動で画面を書き換える指示は不要になります。
// View側:データと見た目のルールを宣言しておく(バインディング)
struct WishButtonView: View {
@ObservedObject var viewModel: WishViewModel
var body: some View {
Button(viewModel.isWishListed ? "お気に入り解除" : "お気に入り登録") {
viewModel.toggleWish()
}
.background(viewModel.isWishListed ? Color.gray : Color.blue)
}
}
// ViewModel側:状態を更新すると、SwiftUIが変更を検知してViewを更新する
class WishViewModel: ObservableObject {
@Published var isWishListed: Bool = false
func toggleWish() {
isWishListed.toggle()
}
}
見た目を手動で書き換える指示コード(buttonTitle = ... など)を書く必要が一切なくなるため、Viewが非常にすっきりします。
2. ロジックをViewModelに寄せることで仕様変更の影響範囲を抑えやすい
Viewには「ボタンが押された」というユーザー操作を伝えさせ、
状態変更やAPI通信などの処理判断はViewModel側に集約するのが基本です。
View側に判断(ロジック)を書いている例
Viewが状態を自ら判断して、処理を呼び分けてしまっている例です。
// View側で状態を確認して処理を分岐している
Button("お気に入り") {
if viewModel.isWishListed {
viewModel.unwish()
} else {
viewModel.wish()
}
}
⭕️ ViewModel側に判断を集約する例
判断ロジックをViewModelに移動させ、Viewからは「実行したい意図」だけを呼び出します。
// WishView.swift
Button("お気に入り") {
viewModel.toggleWish().toggleWish()
}
// WishViewModel.swift
final class WishViewModel: ObservableObject {
@Published var isWishListed = false
func toggleWish() {
if isWishListed {
unwish()
} else {
wish()
}
}
private func wish() {
isWishListed = true
}
private func unwish() {
isWishListed = false
}
}
この設計にすることで、将来的に「お気に入り登録時の条件」などの仕様変更があっても、Viewの修正を最小限に抑えられるため、レイアウトへの影響や修正範囲の拡大を防ぎやすくなります。
また、一覧画面や検索画面など、他の場所にお気に入りボタンを配置する時も toggleWish() を呼び出すだけで処理を再利用しやすくなり、追加開発も進めやすくなります。
まとめ
MVVMや設計パターンに出会った当初は、「なぜこのように分ける必要があるのだろう」と思っていました。
しかし、実際にコードに触れる中で、MVVMは自分やチームメンバーが迷わず開発するための仕組みだと気づきました。
特にデータバインディングの部分は「SwiftUIが宣言的UIである」という点にも関わってきそうです。
今後実務に触れていく中で、さらに学びを深めていきたいと思います。