一些關於 iOS 上的 MVC 的心得⋯

什麼是 MVC?MVC 是我們在寫程式的時候,可以採用的一種程式結構。寫程式的時候,要怎麼設計結構,其實自由度很大。你可以把所有事情都交給同一個物件去做,也可以讓每一個物件都只做一件事。然而,不管是哪一種,都會造成管理上的困難。想想看要從有成千上萬行的原始碼檔,或者成百上千個原始碼檔之中,修復一個 bug 有多困難。所以,好的程式結構,是可以讓程式的內在邏輯更清楚、讓程式更容易維護與管理的。

說到管理,其實用像 Swift 這種物件導向語言寫作,本質上跟做行政管理是很像的。所謂物件導向,就是指你得要設計一個個不同功能的物件,讓他們合作,app 才會動起來。比如說,你可以設計一個負責顯示「按鈕」的 A 元件,然後另外設計一個用來處理按鈕事件的 B 元件;app 啟動的時候,會叫 A 元件去把按鈕顯示出來,並讓 B 元件待命。當使用者按下按鈕的時候,B 元件就會開始反應,去執行跟該按鈕相關的工作。

於是,在設計物件的時候,其實就是在決定哪個物件要負責什麼事情。其中,MVC 就是在 iOS 系統底下最主流的一種權責系統。MVC 來自於三個英文字的縮寫:ModelViewController。這三個字,代表了這個架構底下的三大責任種類;每個元件如果不是 Model 或 View 的話,就是 Controller。前面所提到的按鈕例子中,「按鈕」本身是 View 類的元件,而負責處理按鈕事件的 B 元件則是 Controller 類的。負責顯示按鈕的 A 元件則是很模稜兩可,因為在 iOS 中,最常見的 Controller 類元件就叫做「View Controller」,是跟 View 元件走很近的一種 Controller。也就是說,A 元件可能是 View,也可能是屬於 Controller 類的 View Controller。

在 MVC 中,很重視各個元件之間的權責分明。比如說,View 類元件就最好完全不要管 Model 長什麼樣子,也不需要知道哪個按鈕的功能是什麼,只要知道按鈕按下去的時候會變成什麼顏色,並且在按鈕被按下去的時候顯示變色效果並通知 Controller 就好了。有點像是人類身體的反射動作這樣。Model 類也一樣,不應該知道自己會被怎麼顯示,只知道自己擁有什麼資料,並且在資料改變的時候告訴 Controller 這樣。

那 Controller 呢?什麼事情都要跟 Controller 說,這樣它不是很忙嗎?這的確是 iOS 開發很容易碰到的問題,因為好像所有的事情都可以丟給 Controller 類元件來處理,導致 Controller 類元件很容易變得很肥大。但其實,如果能夠把 Controller 類內部的權責一條條弄清楚的話,是可以分出許多個小 Controller 或者 Helper 元件出來幫助管理的。比如說,iOS 的 View Controller 就是一個負責管理 View 的 Controller,那我們是不是也可以有個負責管理 Model 的 Controller 呢?這樣的話,就不會讓 View Controller 變得太臃腫了。

那麼,為什麼要把權責分得那麼清楚呢?很簡單,跟行政管理一樣,就是容易找到問題源頭。如果負責儲存資料的元件就只有一個,而且該元件就只做儲存資料這件事的話,那碰到資料沒辦法儲存的時候,就只要去找這個元件修理就好了。然而,如果每個元件都有能力去儲存資料的話,那要找出這個問題的話,就得要地毯式的搜索了。

另一個原因是,如果我今天想要針對按鈕做重新設計的話,只要權責分清楚,我就只需要改動按鈕元件本身,不需要去更改 Controller 或 Model。假設每個元件都可以操控按鈕外觀的話,那一次就得針對每個有動過按鈕的元件去重寫程式碼,大量增加工程師的工作量。

要達到權責分明,「減少耦合」是必須的。Swift 提供了好幾種方法,讓元件不會太依賴另一個元件:

Protocol

一般來說,一個元件要直接的操控別的元件的時候,需要「擁有」對方。然而,直接擁有另一個元件的話會讓自己對該元件產生依賴。這時,我們可以定義 Protocol(協定),讓元件去擁有一個符合協定的對象,卻又不需要知道該對象的真身是誰。這樣的話,只要符合該 Protocol,要怎麼換 Protocol 背後的元件都沒問題。

比如說,我們可以定義一個叫做「ButtonResponder」的協定,並約定說只要擁有「didPressButton()」這個方法的元件都可以符合這個協定,那我們的按鈕只要擁有一個屬於「ButtonResponder」的持有物,它不需要知道到底誰是 ButtonResponder 也可以叫它執行「didPressButton()」這個方法。而唯一知道誰是 ButtonResponder 的,就只有指派某物件去當該按鈕持有物的 Controller 類元件了。

Notification

Notification 這個東西我用的不多,通常是用在鍵盤顯示或消失之類的事件反應上面。它就是不讓兩個元件之間有任何直接的關係,另外新增一個訊息中心元件,讓所有的事件都化成訊息,通過訊息中心元件去通知給所有有訂閱該通知的元件。好處是耦合度相當低,缺點則也是耦合度太低了,高自由的結果就是結構容易不明朗。畢竟誰都可以去訂閱通知的話,那權責又會開始不分明了。

Key-Value Observing

我覺得就是一對一版的 Notification。某元件可以去觀察它的持有物的持有物,當該持有物產生變化的時候,某元件就會收到通知。看似方便,但我沒用過。

Closure

這就有趣了。Closure 其實就是物化了的函式,可以從一個元件傳到另一個元件裡。換句話說,就是 A 元件把一些只有自己能做的事打包起來丟給 B 元件,讓 B 元件有需要的時候去執行這個閉包,而又不需要知道閉包到底長什麼樣子。實作上,Closure 很像是函式版的 Protocol。拿 ButtonResponder 的例子來說,按鈕並不需要有一個屬於 ButtonResponder 的持有物,它只需要一個叫做「didPressButton」的閉包。Controller 會把原例中負責當 ButtonResponder 的元件的 didPressButton() 方法當作閉包傳給按鈕,那當按鈕被按下去的時候,它就只需要執行自己的 didPressButton 這個閉包就好,連誰在閉包裡面執行什麼事都不用知道。


我覺得 Notification 跟 KVO 太 Objective-C,寫起來不順,所以多用 Protocol 跟 Closure 在建構整個 app。搭配上 Property Observer,其實已經足夠建構一個還過得去的 MVC 系統。

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.