【Swift】MVVMアーキテクチャとは?ViewModelの役割
この記事からわかること
- MVVMとは?
- Model View ViewModelの役割
- 特徴やメリット
- 設計方法と実装例
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
アプリ開発を行う上で重要になってくる設計思想(アーキテクチャ)の1つである「MVVM」についてまとめていきます。
おすすめ記事:【GoF】23種類のデザインパターンとは?Swiftでよく使う活用例
おすすめ記事:SOLID原則とは?Swift(iOS)で理解する設計開発思想
MVVMとは?
MVVMとは「Model View ViewModel」の略称でアプリなどのソフトウェア開発に適応される設計思想(アーキテクチャ)の1つです。アプリの開発は1人で行う場合もあればチームで開発する場合もあります。どちらにおいても似たようなコードが複数記述されていたり、オブジェクト同士の依存が強い場合など、コード自体の可読性や拡張性が低いと改修コストが大幅に増えてしまいます。アーキテクチャに沿った設計をすることで、拡張性や保守性、作業性、再利用のしやすさなどが向上するというメリットが受けられます。
さらにその中でもMVVMとはアプリケーションなどといった画面への描画処理を持ったソフトウェアにおけるGUI(Graphical User Interface)アーキテクチャの1つです。他にもMVCやMVPなど種類がありますが、MVVMはプログラムを3つの責務に分かれた構造で設計する考え方です。それぞれの責務は大まかにみると以下のように分かれます。
MVVM の3つの要素
- Model:データ
- View:画面
- ViewModel: ModelとViewの橋渡し
そしてそれぞれの頭文字「Model View ViewModel」をとってMVVM(アーキテクチャ)と名付けられています。
ちなみに別の設計思想であるMVCもiOSアプリ開発ではよく用いられます。公式によるとUIKitフレームワーク自体がMVCアーキテクチャーに準じた構造で設計されているようです。またMVCは比較的取っ付きやすい構造なので初心者はこちらから学習することをおすすめします。
おすすめ記事:Swift(UIKit)のMVCアーキテクチャーとは?役割と構造まとめ
iOSアプリ開発とアーキテクチャ
Swiftを使用したiOSアプリ開発ではMVVMに倣って開発されることが多いです。Swift UIが発表されてからはMVVMとの親和性は少し下がっているようですが、UIKitベース(Storyboard)で開発する際にはMVVMがよく使われている印象です。
アーキテクチャに準じない設計で開発していく場合、UIKitベースでは画面を管理するViewControllerクラス内のコードが肥大化しやすく、流れも掴みづらい構造になりがちです。ViewControllerクラスというどっちつかずの命名も責務の境界をぼんやりささている原因の1つかもしれません。
MVVMに限らずアーキテクチャに倣った開発を行うことでコードの目的と役割が明確になり、予期せぬバグを抑止できたり保守性の高いアプリ開発が可能になります。
またRxSwiftと呼ばれるライブラリ(後述しています)がMVVMとセットで使用されることが多いです。
MVVMを構成する3つの要素
MVVMは「Model・View・ViewModel」の3つの要素に分かれていました。これらの役割や持たせるべき処理などを具体例を見ながらもう少し深掘りしていきます。
また実装例としてクラスプラットフォームデータベースライブラリである「Realm」を使用したコードも載せておきます。
おすすめ記事:【SwiftUI】Realm Swiftとは?導入方法とCRUD処理のやり方
Model
Model(モデル) とはデータ部分を司っている要素です。データベースに格納されている情報や通信処理などがまさしくモデルで扱う部分であり、データにおけるCRUD(クラッド:Create, Read, Update, Delete)などの処理も実装します。
例えば取得したデータを表示するために整形するのもこの要素の努めになります。日付のフォーマットや数字の表し方など、他の部分にはデータに関するロジックは含ませないようにしてモデル内でデータに関するロジックを終了させます。モデルから受け取ることができる状態のデータがそのまま使われるようにすることを意識して作成するのが大事になるのだと思います。
具体例:ネットワークAPI、ローカルデータベースなど
import UIKit
import RealmSwift
class RealmDataBaseModel {
private let realm = try! Realm()
// MARK: - Create
public func createRecord(notice:Notification){
try! realm.write {
realm.add(notice)
}
}
// MARK: - Read
public func readIdRecord(id:UUID) -> Notification{
return realm.objects(Notification.self).where({$0.id == id}).first!
}
// MARK: - Update
public func updateRecord(noticeRecord:Notification,body:String,date:Date){
try! realm.write {
noticeRecord.body = body
noticeRecord.date = date
}
}
// MARK: - Delete
public func deleteAllOldRecord(){
try! realm.write{
let result = realm.objects(Notification.self).where({$0.date < Date()})
realm.delete(result)
}
}
}
View
Viewはユーザーの目に実際に触れる部分を司っている要素です。View自体にデータは保持しておらず、Modelのデータを参照することでページを構築していき、Modelのデータが変更になれば都度表示も切り替えることで動的に変化するページが作成されます。
またユーザーと直接やり取りするインタラクティブ(対話的)な部分でもあります。ユーザーの入力操作やタップ操作などのイベントを検知します。
具体例:アニメーション、ユーザーインタラクションなどUI関連
import SwiftUI
import RealmSwift
struct EntryButtonView: View {
// MARK: - ViewModels
private let notificationViewModel = NotificationViewModel()
private let realmDataBaseViewModel = RealmDataBaseViewModel()
private let displayDateViewModel = DisplayDateViewModel()
// MARK: - receive data
public var notice:Notification? = nil
// MARK: - Input
@Binding var date:Date
@Binding var text:String
var body: some View {
Button(action: {
if !text.isEmpty {
var currntItem:Notification
if notice != nil {
currntItem = realmDataBaseViewModel.updateRecord(id:notice!.id,body: text, date: date)
}else{
currntItem = realmDataBaseViewModel.createRecord(body: text, date: date)
}
let dateStr = displayDateViewModel.getNoticeFormatString(currntItem.date)
notificationViewModel.createNotification(id: currntItem.id, body: currntItem.body, dateStr:dateStr)
}
}, label: {
Text(notice != nil ? "更新" : "登録")
.fontWeight(.bold)
.padding(10)
})
}
}
ViewModel
ViewModelはViewとModelの仲介を行う部分です。Viewに表示させるためのデータをModelから受け取る役割や逆にViewから受け取ったら入力を適切にModelへと伝達する役目を待っています。Viewとはデータバインディングなどの仕組みを利用して同期的なデータの更新を実装します。
具体例:データのフィルタリング、ソート、および検索などをしてViewに渡す
import UIKit
import RealmSwift
class RealmDataBaseViewModel {
// MARK: - Models
private let model = RealmDataBaseModel()
// MARK: - Create
public func createRecord(body:String,date:Date) -> Notification{
let notice = Notification()
notice.body = body
notice.date = date
model.createRecord(notice: notice)
return notice
}
// MARK: - Update
public func updateRecord(id:UUID,body:String,date:Date) -> Notification{
let noticeRecord = model.readIdRecord(id: id)
model.updateRecord(noticeRecord: noticeRecord, body: body, date: date)
let newNoticeRecord = model.readIdRecord(id: noticeRecord.id)
return newNoticeRecord
}
// MARK: - Delete
public func deleteAllOldRecord(){
model.deleteAllOldRecord()
}
}
MVVMの特徴とメリット
- データバインディング
- View部分の肥大化を防げる
MVVMの1番の特徴はデータバインディングです。データバインディングとはデータと対応するオブジェクトを紐づけることで同期的な仕組みを持たせることで、データの変更がそのままビューにも適応されるような設計を構築できます。
データバインディングには単方向バインディングと双方向バインディングがありますがこれは片側への反映のみか相互に反映させるかの違いです。
RxSwiftとMVVM
Swiftで MVVMに倣った設計を実装するためによく利用されるライブラリがRxSwiftです。MVVMの肝となるデータバインディングやイベントの検知など、様々な機能を実装できリアクティブプログラミングな開発を手助けしてくれます。
RxSwiftは学習コストが高く使いこなせるようになるまでには時間がかかりますが実践でもよく使われているライブラリなので学習して損はないような気がします。
おすすめ記事:RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する
MVVM設計を実装する際のポイント
- 各要素は疎結合を意識する
- View-ViewModel間はデータバインディング
- Model-View間は直接的に関わらない
- Model-ViewModel間はデータを渡し合う
- ViewModelが仲介役
- Modelは依存性をなくす
まとめると・・・
ModelとViewModelの関係は、Modelがアプリケーションの状態やデータを管理し、ViewModelがViewとModelの間の仲介を行い、Viewがユーザーインタフェースを提供する役割を担う
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。