iOS Design Patterns | #1 MVC, MVP, MVVM, Coordinator, VIPER

黃暉德 Wade Huang
10 min readApr 12, 2024

--

前言

身為軟體工程師一定聽過 design patterns,其中比較廣泛應用在 iOS 上的 design patterns 主要是 MVC, MVVM, MVP..等,但哪一個比較好呢?其實這題沒有標準的答案,這必須根據你目前的專案或你目前的團隊而定。

因此,最迫切的是要先了解每一個 pattern 想要解決什麼事情,pattern 彼此之間的差異是什麼,接下來會將在網路上收集的資料整理成心得筆記做分享,當然這些架構的描述有可能我的理解和你的認知不太一樣,但這都很正常,最重要的是找到最適合你的專案以及實作。

目錄

  • Model-View-Controller
  • MVC 的 Controller 的問題
  • Model-View-Presenter
  • Model-View-ViewModel
  • Presenter vs ViewModel
  • UINavigation 的問題
  • Coordinator
  • VIPER

Model-View-Controller

在 1978 年,MVC 是由 Trygve Reenskaug 提出,當時是為 Smalltalk 程式語言發明的軟體架構,目的是透過將應用程式分成三種組件:Model, View, Controller,並定義三個組件之間的交互作用,來使程式的重複利用性增加,更容易地進行程式後續的修改和擴充。

雖然 Apple 已將 developer library 標示為 retired,不過很多 iOS SDK 還是可以發現 MVC 的影子。

https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html#//apple_ref/doc/uid/TP40008195-CH32-SW1

Model

將特定的資料封裝成資料結構和定義其資料的業務邏輯(新增/修改/更新/刪除)。

例如:
Data structure(class, struct),
Data update..等。

View

專門處理 UI 的顯示。

例如:
UIView, UIStackView, UIButton, UITextField, UILabel, UITableView..等。

Controller

作為 ModelView 的溝通橋樑。Controller 處理來自 View 的使用者操作,並通知 Model 進行資料更新,Controller 接收來自 Model 的更新,並將資料更新到 View 上。

例如:
View life cycle (viewDidLoad, viewWillAppear..),
Setup UI/Animation (auto layout, frame, width, height, backgroundColor..),
UIViewController,
UINavigation (push, pop, present, dismiss..),
Data conversion (parse JSON, encode, decode..),
Data source/Delegate (UITableViewDataSource, UITableViewDelegate..),
API call(Network, CoreBluetooth..),
Business logic..等。

iOS 中的 UIViewController 通常被歸類在 MVC 的 Controller。

MVC 的 Controller 的問題

依據 MVC 的職責劃分理論上很不錯且容易,但在 iOS 和 Xcode 的開發環境下不容易實現,因為 Apple 將大量的業務都放到Controller (UIViewController) 進行,當功能越來越複雜時,Controller 就會被大量的商業邏輯塞滿,導致 Controller 過於肥大而難以維護和測試,因此 MVC 常被戲稱為 Massive-View-Controller。

Controller 是否應該依據職責再進行劃分呢?是的,既然都叫做 UIViewController,那應該將關於處理 UI 的邏輯在切分出來,不是處理 UI 的邏輯讓另一個人來負責,並由這個人來與 UIViewController 進行溝通。

所以我們將 Controller 裡不是處理 UI 邏輯的 API call, Business logic, Data conversion 移出來給這個人負責,而這個人就是 PresenterViewModel

Model-View-Presenter

MVP 是由 MVC 演變而來,主要是將 MVC 的 Controller 再進行職責劃分,解決 Controller 肥大的問題。

Model

和 MVC 的 Model 一樣。

View

專門處理 UI 的顯示,但和 MVC 的 View 些許不同,因為 UIViewController 被歸類在這。

這裡的 UIViewController 只負責處理 UI 的邏輯。

Presenter

和 MVC 的 Controller 一樣作為 ModelView 的溝通橋樑,但和 Controller 不同的是,Presenter 只處理不是 UI 的邏輯,例如:API, Business logic, Data conversion..。

Model-View-ViewModel

MVVM 是由 MVC 演變而來,主要是將 MVC 的 Controller 再進行職責劃分,解決 Controller 肥大的問題。

Model

和 MVC 的 Model 一樣。

View

和 MVP 的 View 一樣,UIViewController 被歸類在這。

ViewModel

和 MVC 的 Controller 一樣作為 ModelView 的溝通橋樑,但和 Controller 不同的是,ViewModel 只處理不是 UI 的邏輯,例如:API, Business logic, Data conversion..。

Presenter vs ViewModel

其實 PresenterViewModel 被分配的職責是一樣的,但兩者的資料傳遞方式是不一樣的。

Presenter 透過 delegate, closure block, target action 來傳遞資料。
ViewModel 透過 data binding 的方式來傳遞資料,例如:Combine, RxSwift 等。

UINavigation 的問題

切換畫面的時候,可以在 UIViewController A 初始化 UIViewController B 後進行 push/present,若要從 UIViewController B 畫面切回來的話,可以 UIViewController B 在初始化 UIViewController A 後進行 pop 或 dismiss UIViewController B 自己。當然,可能也會在 completion closure 的 block 加入某些邏輯。

但仔細想想其實有點怪怪的,因為依據 SOLID 原則,UIViewController 應該只關注自己的職責,也不會知道其他 UIViewController 的存在,因此這樣的寫法會變得沒有彈性且難以維護,也無法重複使用。

若今天突然需要增加幾十個畫面在 UIViewController A 和 UIViewController B 之間,不可能要在這幾十個 UIViewController 裡進行初始化和 push/pop/present/dismiss 吧。

所以我們應該將切換畫面的職責切割出來給某一個人去做管理,這個人管理每一個 UIViewController,負責畫面的切換和順序,它就是 Coordinator

Coordinator

Coordinator 的職責是初始化和管理所有的 UIViewController,負責所有 UIViewController 的呈現和切換,UIViewController 彼此之間不知道彼此的存在,也不用負責切換畫面的工作。

當然 Coordinator 底下可以有子Coordinator,例如:新會員需要有一個 Coordinator 處理註冊流程,老會員登入需要另一個Coordinator 處理登入流程。

所以若要從 UIViewController A 切換到 UIViewController B,UIViewController A 要通知 Coordinator,由 Coordinator 進行判斷來決定下一個畫面是誰,如果是 UIViewController B 的話,就初始化並切換到該畫面。

Coordinator 的 design pattern 非常容易實作,也可以與 MVC, MVVM, MVP 共同運作,網路上常常看到的 MVVM-C 和 MVP-C 其實就是加入 Coordinator,是非常值得學習和實作的 design pattern 之一。

若以 MVVM-C 為例,工作職責可以參考下方圖片。

VIPER

MVVM-C 和 MVP-C 看起來已經將職責劃分得更清楚了,但還有沒有其他可以再劃分出來的職責呢?當然有。

若把特定的業務邏輯和商業邏輯拉出來,也就是 API call, Business logic, Data update 拉出來讓另一個人負責,這就是常見的 Service, Manager, Helper,而在 VIPER 中就是 Interactor

View

和 MVVM-C 的 View 一樣。

Interactor

常見取名為 Service, Manager, Helper 的業務邏輯或商業邏輯。

Presenter

和 MVP-C 的 Presenter 類似,但不需要負責業務邏輯。Presenter 處理ViewInteractor 之間的資料轉換,同時呼叫 Router 來幫忙處理畫面的切換。

Entity

和 MVVM-C 的 Model 一樣,就是資料結構。

Router

和 MVVM-C 的。Coordinator 一樣,負責畫面之間的切換。

總結

從以上的整理可以看出,都圍繞著 SOLID 原則,將職責劃分避免業務耦合,增加程式的可維護性和重複使用性,這些常見的 design patterns,都能運用在不同場景的 iOS App 中,沒有誰比較爛誰比較讚,一切取決於你的專案或團隊。

--

--