1. 程式人生 > >Swift黑科技:還在爭論MVC和MVVM?博主獨創幽靈架構MV!

Swift黑科技:還在爭論MVC和MVVM?博主獨創幽靈架構MV!

本人原創,長文慎入,但此文可能會改寫你的程式設計風格。我認為資料和模型互動的關鍵問題是如何處理資料來源和檢視源本身的異構性。通過面向協議程式設計的不斷實踐,總結他人的理論經驗,我發現了使用兩個極簡的通用協議可以完美破解異構的問題,也就是本文想要介紹的MV架構。在最初版的版本中我想把這個架構命名為MVP(Model-View-Protocol),因為本文瀏覽的人比較多,這個命名容易和現有的MVP(Model-View-Presenter)造成混淆,但其實二者有著天壤之別,由於在本架構中Protocol部分的語法非常簡短精煉,資料的處理分發給了Model和View本身,所以我覺得P可以去掉了,這個架構就叫MV,如幽靈般鬼魅。下面是原文內容:
WWDC2015已經過去一段時間了,我發現自從更新了Swift2.0到現在的Swift2.2,我只是跟著版本更新了所有需要更新的語法,依舊自以為是很熟練的Swift程式設計師。剛入職比較閒碰巧看到了1月份的中國首屆Swift大會上大牛們的分享,突然陷入了思考,有了很多新想法又重溫了幾遍WWDC2015大會的視訊,尤其是408和414號視訊!!!我下定決心重構自己的程式碼,下面步入正題,結合Swift開發大會的一些分享,讓我們談談架構。
通過一個簡單的Demo:一個事件提醒的小應用。
這個應用會使用一個TableView混合展示一個時間段的所有待辦事宜和這個時間段的節日提醒,由於待辦事件和節日的資料構成是不同的,所以需要建立兩個模型,它們在TableView上展示的樣式也應該有所不同,很常規的我們還需要一個TableViewCell的子類。
現在資料工程裡面的目錄是這樣的:
這裡寫圖片描述

模型程式碼:

struct Event {
    var date = ""
    var eventTitle = ""
    init(date:String,title:String){
        self.date = date
        self.eventTitle = title
    }
}

struct Festival {
    var date = ""
    var festivalName = ""
    init(date:String,name:String){
        self.date = date
        self
.festivalName = name } }

為了簡單我都使用了String型別的資料,至於為什麼要使用struct而不使用class,大家可以參考WWDC2015的414號視訊,講的非常清楚,我自己的專案中的資料模型已經全部轉成struct了,我會在後面專門寫博文講解struct,這裡就不贅述了。這裡需要囉嗦一下,注意建立的時候使用的是字面量的方法,而不是可選型,我一直認為使用字面量的方法是更好的選擇,可選型很容易被當做語法糖濫用。尤其是資料的初始化中,你確定你真的需要一個空值?拿一個空值能做什麼?做某種標誌位麼?請和你的後臺開發人員商議,讓他給你一個Bool型別的標誌位,而不是一個空值。在可能的情況下,給你的資料模型的屬性賦一個語義明確的字面量初始值,比如這裡我們使用空字串作為初始值。如果你的資料只是做展示的不會存在修改情況,你也可以使用如下的方法做初始化,以達到效率的最大化:

struct Event {
    let date:String
    let eventTitle:String
    init(date:String = "",eventTitle:String = ""){
        self.date = date
        self.eventTitle = eventTitle
    }
}

在Swift1.2版本之後,let定義的資料也支援延遲載入了,這裡使用了預設引數值做非空的保障。
模型否則在建立一個例項的時候各種可選型的解包或可選繫結會讓你吃盡苦頭,空值的訪問是程式carsh的元凶!
如果如果你更新了Xcode7.3,你會發現在建立一個屬性的時候Xcode的提示是“ =“,沒錯,Xcode推薦你用字面量去做初始化。
有了資料模型後,在Cell上建立兩個Label

class ShowedTableViewCell: UITableViewCell {
    //用來展示事件主題或節日名稱的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用來展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!


}

MVC架構:
從這裡我們將展示傳統的MVC的寫法,但是包含了一些關鍵的知識點,所以還是建議您不要跳過。我們通過控制器中的程式碼去控制資料的展示,由於資料來源包含兩種資料型別,可以構造兩個陣列避免陣列的異構:

    var eventList = [Event]()
    var festivalList = [Festival]()
    let loadedEventList = [Event(date: "2月14", eventTitle: "送禮物")]
    let loadedFestivalList = [Festival(date: "1月1日", festivalName: "元旦"),Festival(date: "2月14", festivalName: "情人節")]

這裡使用了struct的預設構造器構造物件,有兩個節日提醒:元旦節和情人節,元旦節沒什麼事情做,情人節那天有個事件提醒”送禮物“,我們使用GCD去模擬資料重新整理,整個控制器的程式碼如下:

import UIKit

let cellReusedID = "ShowedTableViewCell"
class ShowedTableViewController: UITableViewController {

    var eventList = [Event]()
    var festivalList = [Festival]()
    let loadedEventList = [Event(date: "2月14", eventTitle: "送禮物")]
    let loadedFestivalList = [Festival(date: "1月1日", festivalName: "元旦"),Festival(date: "2月14", festivalName: "情人節")]
    override func viewDidLoad() {
        super.viewDidLoad()
        let delayInSeconds = 2.0
        let popTime = dispatch_time(DISPATCH_TIME_NOW,
            Int64(delayInSeconds * Double(NSEC_PER_SEC)))
        dispatch_after(popTime, dispatch_get_main_queue()) { () -> Void in
            self.eventList = self.loadedEventList
            self.festivalList = self.loadedFestivalList
            self.tableView.reloadData()
        }
    }



    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return eventList.count + festivalList.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell
        //傳統的MVC,你需要在這裡處理資料本身的同構與異構情況,還得處理資料與檢視的邏輯關係
        //這裡我們把事件提醒放在節日的前面展示
        if indexPath.row > eventList.count - 1{
            cell.MixLabel.text = festivalList[indexPath.row - eventList.count].festivalName
            cell.dateLabel.text = festivalList[indexPath.row - eventList.count].date
            cell.backgroundColor = UIColor.whiteColor()
            return cell
        } else {
            cell.MixLabel.text = eventList[indexPath.row].eventTitle
            cell.dateLabel.text = eventList[indexPath.row].date
            cell.backgroundColor = UIColor.redColor()
            return cell
        }
    }


}

執行一下看看:
這裡寫圖片描述

似乎還不錯,我們把兩個不同的資料結構展現在一張頁面上了,並且複用了cell,但是設定cell的代理方法中的程式碼似乎有點多,而且如果我需要按照時間去排序,那麼兩個同構的陣列作為資料來源不好排序,那麼重構首先從把同構變成異構開始。由於struct沒有繼承,按照Swift2.0的精神,此時我們需要提煉兩個資料模型的共性,方法是利用protocol,觀察到Event和Festival都有date屬性,所以寫一個協議:

protocol HasDate{
    var date:String {get}
}

這裡這個協議只有一個屬性date,Swift協議中定義的屬性只有宣告,遵守協議的物件必須實現這個屬性,但是不限於儲存屬性還是計算屬性。協議中定義的屬性必須指定最低的訪問級別,這裡的date必須是可讀的,至於可寫的許可權取決於實現該協議的資料型別中的定義。由於我們的Event和Festival都具有了date屬性,直接讓二者遵守HasDate協議,不要用擴充套件的方式讓二者遵守協議,編譯器報錯的,很怪0 0.
修改並化簡控制器中的資料來源,使用異構資料來源,現在控制器的程式碼如下:

import UIKit

let cellReusedID = "ShowedTableViewCell"
class ShowedTableViewController: UITableViewController {

    var dataList = [HasDate]()
    var loadeddataList:[HasDate] = [Event(date: "2月14", eventTitle: "送禮物"),Festival(date: "1月1日", festivalName: "元旦"),Festival(date: "2月14", festivalName: "情人節")]
    override func viewDidLoad() {
        super.viewDidLoad()
        let delayInSeconds = 2.0
        let popTime = dispatch_time(DISPATCH_TIME_NOW,
            Int64(delayInSeconds * Double(NSEC_PER_SEC)))
        dispatch_after(popTime, dispatch_get_main_queue()) { () -> Void in
            //注意這裡,我故意把loadeddataList中的資料打亂了,為了實現異構資料的按照某個公共型別的屬性的排序,使用了Swift內建的sort函式,String遵守了Compareable協議,這裡為了簡單吧date指定為String型別,如果是NSDate,你可以在sort的閉包中指定合適的排序規則。
            self.dataList = self.loadeddataList.sort{$0.date < $1.date}
            self.tableView.reloadData()
        }
    }



    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell
        //注意這裡,通過可選繫結進行異構資料的型別控制
        if let event = dataList[indexPath.row] as? Event{
            cell.MixLabel.text = event.eventTitle
            cell.dateLabel.text = event.date
            cell.backgroundColor = UIColor.redColor()
            return cell
        } else if let festival = dataList[indexPath.row] as? Festival{
            cell.MixLabel.text = festival.festivalName
            cell.dateLabel.text = festival.date
            cell.backgroundColor = UIColor.whiteColor()
            return cell
        } else {
            return cell
        }
    }
}

執行一下:
這裡寫圖片描述
沒有任何問題。對異構陣列的型別判斷的寫法來自於WWDC2015上的408號視訊,現在控制器裡的程式碼已經精簡了很多了,我們解決了異構的問題,對於MVC來說,這似乎已經精簡到極限了。這是一個簡單的Demo,在真正的工程中一個控制器當中的程式碼可能有幾百上千行,或者有多個TableView,這個時候MVC的弊端就顯現了,在幾百行程式碼中可能有一百行都用來做資料與檢視的繫結,而資料模型和檢視本身的程式碼定義中卻只有寥寥數十行,控制器的負擔太重了!因此有人提出了將控制器中有關模型與檢視的邏輯的程式碼提出到一個單獨的區域進行處理,這就是MVVM架構的由來。
MVVM架構
對MVVM架構的解讀我想引用Swift開發者大會上李信潔前輩的示例寫法,通過POP來實現一個MVVM,並且對其寫法進行了一些精簡。我們先不修改View和Modal的程式碼,因為需要更新的是一個cell,所以首先需要寫一個傳遞Modal中資料的協議:

protocol CellPresentable{
    var mixLabelData:String {get set}
    var dateLabelData:String {get set}
    var color: UIColor {get set}
    func updateCell(cell:ShowedTableViewCell)
}

這個協議的思想是顯示地宣告一個更新cell的方法,並根據cell需要的資料宣告兩個屬性,我們並不關心mixLabel和dateLabel的資料從哪裡來,叫什麼名字,但他們的功能是確定的,Swift2.0之後可以擴充套件協議,下面通過協議擴充套件給這個協議增加預設的實現,這樣在繫結資料時可以減少程式碼量:

extension CellPresentable{
    func updateCell(cell:ShowedTableViewCell){
        cell.MixLabel.text = mixLabelData
        cell.dateLabel.text = dateLabelData
        cell.backgroundColor = color
    }
}

好了,我們寫好了,下一步我們要修改cell的程式碼,增加一個方法接受一個CellPresentable:

class ShowedTableViewCell: UITableViewCell {
    //用來展示事件主題或節日名稱的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用來展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func updateWithPresenter(presenter: CellPresentable) {
        presenter.updateCell(self)
    }
}

這裡也做了一些改進,李信潔前輩的示例中是針對每一個控制元件去定義方法的,其實對一個View的所有IBOutlet做更新不就是更新它自己麼,所以這裡我的寫法是直接傳入self。然後(我也不想多說然後,但是步驟就是這麼多)為了繫結異構的Model和View你還需要定義一個ViewModel,並且通過定義不同的init實現資料繫結:

struct ViewModel:CellPresentable{
    var dateLabelData = ""
    var mixLabelData = ""
    var color = UIColor.whiteColor()
    init(modal:Event){
        self.dateLabelData = modal.date
        self.mixLabelData = modal.eventTitle
        self.color = UIColor.redColor()
    }
    init(modal:Festival){
        self.dateLabelData = modal.date
        self.mixLabelData = modal.festivalName
        self.color = UIColor.whiteColor()
    }
}

最後我們終於可以去修改我們的控制器了,控制器中需要更改的是與cell有關的datasource方法:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell
        if let event = dataList[indexPath.row] as? Event{
            let viewModel = ViewModel(modal: event)
            cell.updateWithPresenter(viewModel)
            return cell
        } else if let festival = dataList[indexPath.row] as? Festival{
            let viewModel = ViewModel(modal: festival)
            cell.updateWithPresenter(viewModel)
            return cell
        } else {
            return cell

        }
    }
}

這段程式碼寫的我滿頭大汗,編譯執行,幸運的是執行的結果是正確的:
這裡寫圖片描述

我在想MVVM模式的意義是什麼?我在使用MVVM之前甚至需要考慮一下值不值得花時間去寫成MVVM的模樣,因為MVVM需要給所有的view提供協議,並且將所有的資料模型的繫結過程寫進一個新的資料結構ViewModal中,但其實這個ViewModel的價值非常之小,除了資料繫結,沒有其他作用了,裡面甚至只有空洞的init構造器,我想我已經決定放棄這個思路了。
MV的萌芽階段
我繼續著自己的思考,大會上傅若愚前輩分享的示例給了我很大的啟發,因為他提供了一個沒有中間層的模型!我一直在思考這個模型,並且在入職的第一個專案中一直在按照他的模型來組織自己的程式碼,直到我頓悟了自己的MV模型。下面簡單介紹一下傅若愚前輩的思路,這個思路的優勢在於所有的資料和模型繫結都只需要兩個通用的協議:

//檢視使用的協議
protocol RenderContext{
    func renderText(texts:String...)
    func renderImage(images:UIImage...)
}
//資料使用的協議
protocol ViewModelType{
    func renderInContext(context:RenderContext)
}

上面是大會上傅若愚前輩的原版,在介紹這個協議的用法之前,我覺得應該先做一點點改進,ViewModalType應該改成:

protocol ViewModelType{
    func renderInContext<R:RenderContext>(context:R)
}

這兩個版本都可以通過編譯,差別在執行的效率上,下面我在playground中展示一個示例,這個示例來源於《Advanced Swift》這本書,其實蘋果的WWDC2015 408號視訊中也明確表述了不要把協議當做引數型別,而寫成泛型的約束,但是沒有詳細講解為什麼,下面是示例:

func takesProtocol(x: CustomStringConvertible) { //
    print ( sizeofValue(x))
}
func takesPlaceholder<T: CustomStringConvertible>(x: T) {
    print ( sizeofValue(x))
}

兩個方法,前者使用協議作為引數的型別,後者使用協議作為泛型的約束條件,兩個方法都會列印引數的長度,呼叫一下試試:

takesProtocol(1 as Int16)
takesPlaceholder(1 as Int16)

列印結果:
這裡寫圖片描述
換成類再列印一次:
這裡寫圖片描述
沒錯,由於協議本身既可以被類遵守、也可以被結構體、列舉遵守,也就是說既可以被引用型別遵守也可以被值型別遵守,把協議當做引數型別,實際上會創造一個Box型別,裡面會為引用型別遵守者預留地址也會為值型別遵守者預留地址,甚至需要儲存一個指標長度找到協議的真正繼承型別。而Swift2.0之後編譯器得到了加強,具有了泛型特化的功能,對程式碼中的泛型在編譯時就會確定其真正的型別,不耗費任何效能。
下面我們用改造後的傅若愚前輩的協議來改造Demo,你需要讓你的資料模型去遵守RenderContext,然後根據模型的引數型別將每一個引數存入對應型別方法的引數列表中,這些方法都是可變引數,不限制數量,但是引數的型別是確定的。這種使用引數型別做通用型別的寫法消滅了中間的ViewModel層,把Model和View直接對接了。由於Swift要求每一個協議的遵守者都必須實現協議的全部方法,而有些方法的資料模型並沒有,所以你在使用之前需要使用協議擴充套件為這些方法實現一個空的實現:

protocol RenderContext{
    func renderText(texts:String...)
    func renderImage(images:UIImage...)
}

extension RenderContext{
    func renderText(texts:String...){

    }
    func renderImage(images:UIImage...){

    }
}

現在你的模型應該是下面這樣:

struct Event:HasDate,ViewModelType{
    var date = ""
    var eventTitle = ""
    func renderInContext<R : RenderContext>(context: R) {
        context.renderText(date,eventTitle)
    }
}

struct Festival:HasDate,ViewModelType{
    var date = ""
    var festivalName = ""
    func renderInContext<R : RenderContext>(context: R) {
        context.renderText(date,festivalName)
    }
}

檢視的程式碼應該是這樣的:

class ShowedTableViewCell: UITableViewCell,RenderContext {
    //用來展示事件主題或節日名稱的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用來展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func renderText(texts: String...) {
        dateLabel.text = texts[0]
        MixLabel.text = texts[1]
    }
}

由於遵守了多個協議,所以控制器中原本的異構型別不合適了,此時可以給多個協議型別寫一個別名方便使用,記得順便更新一下你的Model,提高可讀性:

typealias DateViewModel = protocol<HasDate,ViewModelType>

現在控制器中的資料來源可以使用新的異構型別了:

var dataList = [DateViewModel]()
    var loadeddataList:[DateViewModel] = [Event(date: "2月14", eventTitle: "送禮物"),Festival(date: "1月1日", festivalName: "元旦"),Festival(date: "2月14", festivalName: "情人節")]

然後更新cell的代理方法:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell
        dataList[indexPath.row].renderInContext(cell)
        return cell
    }

不錯,程式碼簡潔了很多,執行一下:
這裡寫圖片描述
等等,我們似乎遺漏了一些東西,cell的背景顏色呢?好吧讓我們加上,可是我該去哪裡加呢?去控制器中嗎?不不堅決不能碰控制器,那麼只能去cell中了,現在問題出現了,當兩個模型共享一個檢視的時候,我該如何判斷資料來源從哪裡來?renderText(texts: String…)這樣的寫法已經完全失去了異構的特性,那麼試著這樣寫,在資料傳遞引數的時候多傳一個String好了,反正引數是我們的自由:

struct Event:DateViewModel{
    var date = ""
    var eventTitle = ""
    func renderInContext<R : RenderContext>(context: R) {
        context.renderText(date,eventTitle,"red")
    }
}

這樣在檢驗的時候就看最後一個引數就好了:

class ShowedTableViewCell: UITableViewCell,RenderContext {
    //用來展示事件主題或節日名稱的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用來展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func renderText(texts: String...) {
        dateLabel.text = texts[0]
        MixLabel.text = texts[1]
        if texts[2] == "red"{
            backgroundColor = UIColor.redColor()
        }
    }
}

這裡有個語法糖,可變引數的方法,在取參時不會發生越界,因為Festival的renderText方法只傳了兩個值,執行結果又正常了。那麼如果我粗心把引數寫錯順序了呢?結果成了這樣:
這裡寫圖片描述
如果我的Festival中多了一個Int型別,而Event中恰巧沒有呢?按照值去區分引數不是一個好主意,因為你用下標從一個數組中取值的時候除了它的型別不能得到任何資訊,甚至都不知道這個值存不存在!我再次陷入了思考,既然View需要的是Model中的屬性,這不就等於需要Model自己麼,那麼為什麼我們不能直接傳遞Modal自己呢?
MV!
所以我再次改造了傅若愚前輩的協議,順便把名字改的好辨認一點,原來的名字太容易出錯了- -現在它是這樣子的:

//檢視使用的協議
protocol ViewType{
    func getData<M:ModelType>(model:M)
}
//資料使用的協議
protocol ModelType{
    func giveData<V:ViewType>(view:V)
}

不需要在擴充套件中寫預設實現,因為傳值是相互且確定的,所以方法一定會被實現。
模型是這樣子的:

typealias DateViewModel = protocol<HasDate,ModelType>
struct Festival:DateViewModel{
    var date = ""
    var festivalName = ""
    func giveData<V : ViewType>(view: V) {
        view.getData(self)
    }
}

struct Event:DateViewModel{
    var date = ""
    var eventTitle = ""
    func giveData<V : ViewType>(view: V) {
        view.getData(self)
    }
}

檢視:

class ShowedTableViewCell: UITableViewCell,ViewType {
    //用來展示事件主題或節日名稱的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用來展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func getData<M : ModelType>(model: M) {
        //這裡不能寫成guard let dateModel = model as? DateViewModel else{}令我有些意外
        guard let dateModel = model as? HasDate else{
            //不滿足Cell基本需求的Model直接淘汰掉
            return
        }
        //處理相同屬性
        dateLabel.text = dateModel.date
        //處理資料來源異構
        if let event = dateModel as? Event{
            MixLabel.text = event.eventTitle
            backgroundColor = UIColor.redColor()
        } else if let festival = dateModel as? Festival{
            MixLabel.text = festival.festivalName
            backgroundColor = UIColor.whiteColor()
        }
    }
}

再次用蘋果官方給出的異構判斷方法解決異構,協議不同於類,沒有那麼多繼承上的檢查,所以使用as?是很高效的,最後只要給控制器中的程式碼換個名字就夠了:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID, forIndexPath: indexPath) as! ShowedTableViewCell
        dataList[indexPath.row].giveData(cell)
        return cell
    }

完成,執行效果:
這裡寫圖片描述
你會發現即便你刪掉你的控制器程式碼,View和Model中的邏輯也不會發生改變,而在控制器中進行資料繫結的時候,因為使用了協議來實現資料來源的異構,你甚至不需要對資料來源做篩選,只要它們是使用相同的cell來做展示。
等等,還沒完!即便這個架構已經非常精簡了,但是你也許發現了在Model中我們寫了很多重複的程式碼,也就是giveData的實現部分,這個實現是完全相同的,而且我們不希望這個方法被複習,所以,最後的優化,把giveData的定義和實現放在ModelType的協議擴充套件中:

//檢視使用的協議
protocol ViewType{
    func getData<M:ModelType>(model:M)
}
//資料使用的協議
protocol ModelType{
}
//ModelType的預設實現
extension ModelType{
    func giveData<V:ViewType>(view:V){
        view.getData(self)
    }
}

現在你可以刪掉Event和Festival中的giveData的實現了,Model、View、Controller和Protocol的程式碼都變得極其簡單,依靠Swift強大的語言特性,讓資料的傳遞與異構的處理似乎看不見摸不著,卻又真實地發生了,幽靈般鬼魅。寥寥數行程式碼解決了MVC和MVVM爭論多年的問題,執行一下,享受幽靈架構MV吧!
在專案的最初階段,開發人員拿到的是原型和設計圖,即便我們不清楚該如何開始編寫複雜的處理邏輯,但是資料的狀態與檢視的樣式的對應關係是大致確定的,因此可以直接使用MV架構繫結資料來源和檢視的邏輯,即便後臺開發者提供的最終接口裡有資料的改變,那麼在我們修改對應的Model的時候,View中的程式碼也會以報錯的形式提示你修改,開發的效率會得到一個顯著的提升!
程式碼打包了一份,放這裡了:http://pan.baidu.com/s/1qYTAs3M
有需要的自取
寫在後面:
博主欠了欠身子,從吃完晚飯寫到了半夜,一口氣完成了本文,如果你喜歡我的文章並且得到了啟發,歡迎轉載傳閱,註明出處即可。在Swift1.X時代我覺得Swift脆弱的像只小貓,Swift2.0之後我才突然發現蘋果締造的是一隻野獸。蘋果很聰明,在推進Swift替代OC的道路上採取了溫柔的手段:僅僅是讓Swift變得更優秀。通過不斷鍛鍊自己面向協議程式設計的能力,我有了很多新的體會,想起了迪傑斯特拉老爺子著名的goto有害論,請准許我大膽預言一下,在面向協議的世界中AnyObject也是有害的,點到為止。

關於博主本人:
《Swift開發手冊:技巧與實戰》作者。國內計算機領域的某名校畢業,學習不差,初入社會,曾隻身離校北漂妄圖以退學抗議畸形的研究生教育,後心疼父母返校完成學業。從2014年底開始接觸Swift後一發不可收拾,至今保持狂熱,小人物大夢想,孜孜不倦致力於改善iOS程式設計體驗。歡迎大家留言交流,力所能及之處,必傾囊相授。