iOS 工程

[iOS 架構 #3] SwiftUI 不需要 ViewModel?MV 模式爭論總整理

iOS 社群有一個延續多年的熱門爭論。

閱讀 4 分鐘
[iOS 架構 #3] SwiftUI 不需要 ViewModel?MV 模式爭論總整理 封面圖

iOS 社群有一個延續多年的熱門爭論。

「SwiftUI 需要 ViewModel 嗎?」

上一篇整理了 MVVM 的核心是繫結,而 SwiftUI 已將繫結內建於框架中。於是有人開始主張:「ViewModel 這個層級本身不就是重複嗎?」這就是所謂的 MV(Model-View)模式陣營。

今天作為系列最終篇,將公平檢視雙方論據,並整理出實際判斷時可採用的標準


SwiftUI 的 View 本來就很像 ViewModel

回頭看 UIKit 為什麼需要 MVVM,原因如下。

  • UIViewController 變得過於龐大,因此希望把狀態與邏輯移到外部
  • 需要繫結來同步狀態與畫面

但 SwiftUI 的 View 本來就是狀態的函式body它只接收狀態並回傳畫面宣告,@State值一改變,框架就會自動重新繪製。為了繫結,不需要撰寫 Combine 訂閱程式碼。

此外,SwiftUI 的 View 不是類別,而是值型別 struct。它不是像 UIViewController 那樣可能膨脹到數千行的生命週期大集合,而更像每次渲染都重新建立的輕量設計圖。

MV 陣營的主張由此出發:既然 View 不會膨脹,繫結又是免費的,為每個畫面建立 ViewModel 類別,不就是把 UIKit 的習慣慣性帶過來嗎?


MV 陣營的論據

在 MV 模式中,不為每個畫面配置 ViewModel,而是由 View 直接使用狀態工具。

  • 只屬於單一 View 的狀態放在@State這裡
  • 多個畫面共用的狀態則以@Observable模型@Environment注入
  • 伺服器資料由 Model 層的 store 或 client 負責
struct ProfileView: View {
    @Environment(UserStore.self) private var store
    @State private var isEditing = false

    var body: some View {
        List(store.posts) { PostRow(post: $0) }
            .task { await store.loadPosts() }
    }
}

Apple 的官方範例程式碼(Fruta、Food Truck 等)大多也是這種結構。多個 View 共用一個領域層級的@Observable模型,而不是每個畫面各自擁有 ViewModel。

這種方式的優點很明確:省去每個畫面建立一個類別的樣板程式碼,也能直接使用@State@Binding等框架工具。

SwiftUI 的 View 本來就是狀態的函式
SwiftUI 的 View 本來就是狀態的函式

MVVM 陣營的反駁

反方的論據也不容小覷。

**第一,邏輯會滲入 View。**當「貼文為 0、載入完成且沒有錯誤時顯示提示文字」這類條件開始以三元運算子堆在body裡,即使是值型別,也同樣難以閱讀。

**第二,測試問題。**View 的body很難透過單元測試執行。展示邏輯放在 View 裡,就會一起落入測試盲區。抽到 ViewModel 後,它會成為純 Swift 物件,可以直接驗證。

**第三,規模問題。**如果應用程式有幾十個畫面且由多人協作,就需要一套一致規則來規定狀態與邏輯的位置。每個畫面都有一個稱為 ViewModel 的固定位置,有助於降低協作成本。


不是名稱,而是相依方向

放在雙方論據前看,這場爭論其實不是在討論「是否存在名為 ViewModel 的類別」,而是邏輯與狀態應該放在哪裡。以此作為判斷標準,大多數情況都能釐清。

  • 只屬於單一 View 的 UI 狀態(切換開關、焦點、是否顯示工作表)應@State放在 View 裡。把它抽到 ViewModel 是過度設計。
  • 多個畫面共用的領域狀態則建立成@Observable模型並注入。叫它 Store 或 ViewModel,​​本質都一樣。
  • 當畫面用的轉換邏輯變複雜時(格式化、顯示條件、排序與篩選),就抽成 View 外部可測試的型別。那實際上就是 ViewModel。
  • 無論採用哪一邊,只要遵守View 不直接接觸網路或資料庫的相依方向,MV 或 MVVM 主要只是名稱不同。

總之,「SwiftUI 不需要 ViewModel」實際上可以總結為:不必為每個畫面機械式建立 ViewModel,但這不代表應該移出 View 的邏輯就消失了。

真正的判斷標準不是名稱,而是相依方向
真正的判斷標準不是名稱,而是相依方向

系列總結

三篇 iOS 架構系列可逐篇濃縮如下。

  • MVC:UIViewController 同時扮演 View 與 Controller,因此責任集中。對小型畫面仍然有效。
  • MVVM:將畫面狀態移到 ViewModel,並透過繫結同步。沒有繫結就只完成了一半。
  • MV 爭論:SwiftUI 內建繫結,因此沒有理由強制每個畫面都使用 ViewModel。不過邏輯的位置與相依方向仍然需要設計。

架構不是潮流,而是符合團隊與應用程式規模的取捨選擇。如果反應不錯,之後也會介紹 VIPER 與 TCA 等更重量級的選項。

延伸閱讀