iOS 開發(fā)中的 MVVM 模式——實用進階篇(整理)

這篇文章主要介紹了實際應(yīng)用 MVVM 的過程中的一些問題和解決方案

MVVM(Model View ViewModel)是一種 MVC(Model View Controller)的一種變型,來解決 MVC 中龐大復(fù)雜的 Controller 難以維護的問題。大致上講 MVVM 有幾個要求:

  • Models 不能跟主動對任何對象交流(這點與 MVC 一樣)
  • View Model 只能主動對 Models 交流
  • View Controller 不直接與 Models 交流,只和 View Models 和 Views 交流
  • Views 只與 View Controllers 交流,通知他們用戶交互的事件(這點也和 MVC 一樣)

MVVM 和 MVC 有很多類似的特點,主要的不同有:

  • 有一個 View Model 類
  • View Controller 不能直接接觸 Models

另外一點,MVVM 默認 View 和 View Controller 有一個一對一的關(guān)系,一般我們把這兩個看做一個整體,會以 .swift文件 和 Storyboard 的形式出現(xiàn)。

View Model 的工作是處理所有的展示數(shù)據(jù)的邏輯。如果一個 model 中有一個 NSDate對象,NSDateFormatter就會在 View Model 中用來設(shè)置日期的展示形式。

View Model 不能接觸任何用戶界面的部分,View Model 文件中不應(yīng)該 import UIKit,View Controller 會觀察 View Model 去了解什么時候顯示新的數(shù)據(jù)(通過 KVO 或者 FRP(Functional Reactive Programming))

MVVM 和 MVC 有一個共同的弱點:沒有清楚的定義應(yīng)該把網(wǎng)絡(luò)請求部分放在哪里。在實際操作過程中,我會把網(wǎng)絡(luò)請求放在 View Model 文件里面,但之后我打算把網(wǎng)絡(luò)請求放在自己獨立的一個類中,View Model 文件會擁有這個對象。

下面我們主要談一談實際應(yīng)用 MVVM 過程中一些挑戰(zhàn):

如何構(gòu)造用戶界面

例如你想構(gòu)造這樣一個常用的界面,有一個 segment control 在屏幕頂部,屏幕的其他部分是一個 collection view,選擇不同的 segment,就會展示不同樣式的 collection view,元素的排列順序。我們定義了一個 enum 來枚舉所有的排列樣式:

enum LayoutValues: Int {
    case layout0 = 0
    case layout1
    case layout2
}

那么這個 enum 在 MVVM 模式中應(yīng)該放在哪里呢?因為這個 enum 決定了數(shù)據(jù)排列的順序,每個 cell 中的文字和按鈕的 title,這些都屬于展示的邏輯,所以這個 enum 看起來應(yīng)該放在 view model 中。

然而,這些 layout 并不改變要展示的數(shù)據(jù),只是決定了要呈現(xiàn)的數(shù)據(jù)的排列方式和排列順序,從這個角度上來說 enum 又應(yīng)該放在 view controller 中。

我的解決方法是把 enum 放在 view model 中,然后在 view model 中加一個對外的Observable或者Signal來表示使用了哪個 layout,基于用戶選擇的 segment,view model 更新這個值,然后在 view controller 中根據(jù)相應(yīng)的 layout 改變 collection view 的樣式,view controller 也可以根據(jù)這個值來決定用哪個 cell reuse identifier

如何構(gòu)造 View Model

iOS 開發(fā)者在用 MVVM 和 FRP 寫應(yīng)用的時候最常見的問題可能就是 ViewModel 怎么把數(shù)據(jù)展現(xiàn)給 ViewController。當 Model 層的數(shù)據(jù)發(fā)生變化更新的時候,ViewController 需要得到通知然后做出相應(yīng)的 UI 更新,我們一般會用到兩種機制:

  1. 在 View Model 使用 property,可以用 KVO 來觀察數(shù)據(jù)的變化 (或者用 FRP 把屬性封裝在信號或者序列中)
    2.把信號或者序列當做 property 放在 View Model 中,然后可以用相應(yīng)的第三方庫和框架

第一個選項很吸引人,因為可以在 View Controller 中決定怎么選擇觀察那些 property。然而,我不推薦在 Swift 中使用第一個選項,因為 Swift 在 KVO 中沒有類型檢查,你需要對 AnyObject 強制轉(zhuǎn)換類型很多次。

第二個選項是比較 Swift 的方式,基于 Swift 的 generics 特性,signals,sequences,observables 可以支持編譯過程中的類型檢查。

但有時候在 view model 增加這些 Signals 或者 Observables 有些困難。Swift 的初始化方法對于什么時候?qū)?property 賦值有非常明確的規(guī)定。Signals 或者 Observables 需要使用 view model 內(nèi)部的狀態(tài),所以它們必須在super.init()之后才能創(chuàng)建,但是另一方面,我們在調(diào)用super.init()之前保證所有 property 已經(jīng)被賦值了,包括那些 Signal/Observable property。

這是個先有雞還是先有蛋的問題。

我采用比較簡單的解決方法:定義成var的隱式可選類型,這樣就可以在super.init()之后才給 property 賦值。這不是一個完美的解決辦法。我們可以用lazy var property 的閉包賦值來代替上面的方法。在 Swift 不斷完善和更新的過程中,大家也可以探索其他更好的辦法。

如何處理用戶交互

舉一個很常用的例子,用戶點擊 collection view 中的一個 cell,跳轉(zhuǎn)到詳情頁面。用戶點擊的操作應(yīng)該在 view controller 中處理,具體內(nèi)容是展現(xiàn)一個新的詳情頁面。但是 view controller 不能直接接觸 models,我們要如何用 MVVM 模式實現(xiàn)這樣的用戶交互呢?

我的解決方案是利用 Swift 的閉包。首先在 view model 中定義一個閉包:

typealias ShowDetailsClosure = (Item) -> Void

然后在 view model 中添加一個 property:

class ViewModel {
  let showDetails: ShowDetailsClosure
  init(...
       showDetails: ShowDetailsClosure,
       ...
}

接著我需要調(diào)用閉包,在 view model 中定義一個view controller 可以調(diào)用的函數(shù),這個函數(shù)的參數(shù)是可以決定使用什么數(shù)據(jù),一般情況下常用 index path:

func showDetailsForItemAtIndexPath(indexPath: NSIndexPath) { 
    showDetails(Items[indexPath.item])
}

現(xiàn)在當用戶選中一個 cell,會調(diào)用 view model 中的這個函數(shù),并且傳入 index path 參數(shù),view model 決定使用哪個數(shù)據(jù),并調(diào)用在 view controller 中定義的閉包,例如:

func showDetailsForItem(item: Item) {
    performSegueWithIdentifier(SegueIdentifier.ShowItemDetails.rawValue, sender: item)
}

最后一個問題是怎么創(chuàng)建這個 view model。我們需要傳遞一個閉包給view model 的初始化函數(shù),然后用 lazy loading 來調(diào)用 view model 的初始化函數(shù)。

lazy var viewModel = {
    return ViewModel(..., showDetails: self.showDetailsForItem, ...)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容