2.現(xiàn)在讓我們從MVC開始
幾乎所有的App都只干這么一件事:將數(shù)據(jù)展示給用戶看递沪,并處理用戶對(duì)界面的操作豺鼻。
MVC的思想:一句話描述就是Controller負(fù)責(zé)將Model的數(shù)據(jù)用View顯示出來,換句話說就是在Controller里面把Model的數(shù)據(jù)賦值給View款慨,比如在controller中寫document.getElementById("box").innerHTML = data[”title”]儒飒,只是還沒有刻意建一個(gè)Model類出來而已。
M檩奠、V桩了、C
Model(模型):是應(yīng)用程序中用于處理應(yīng)用程序數(shù)據(jù)邏輯的部分。
通常模型對(duì)象負(fù)責(zé)在數(shù)據(jù)庫中存取數(shù)據(jù)埠戳。
比如我們?nèi)祟愑幸浑p手井誉,一雙眼睛,一個(gè)腦袋整胃,沒有尾巴颗圣,這就是模型,Model定義了這個(gè)模塊的數(shù)據(jù)模型。
在代碼中體現(xiàn)為數(shù)據(jù)管理者在岂,Model負(fù)責(zé)對(duì)數(shù)據(jù)進(jìn)行獲取及存放奔则。
數(shù)據(jù)不可能憑空生成的,要么是從服務(wù)器上面獲取到的數(shù)據(jù)蔽午,要么是本地?cái)?shù)據(jù)庫中的數(shù)據(jù)易茬,
也有可能是用戶在UI上填寫的表單即將上傳到服務(wù)器上面存放,所以需要有數(shù)據(jù)來源祠丝。
既然Model是數(shù)據(jù)管理者疾呻,則自然由它來負(fù)責(zé)獲取數(shù)據(jù)。
Controller不需要關(guān)心Model是如何拿到數(shù)據(jù)的写半,只管調(diào)用就行了岸蜗。
數(shù)據(jù)存放的地方是在Model,而使用數(shù)據(jù)的地方是在Controller叠蝇,
所以Model應(yīng)該提供接口供controller訪問其存放的數(shù)據(jù)(通常通過.h里面的只讀屬性)
View(視圖):是應(yīng)用程序中處理數(shù)據(jù)顯示的部分璃岳。
通常視圖是依據(jù)模型數(shù)據(jù)創(chuàng)建的。
View悔捶,視圖铃慷,簡單來說,就是我們?cè)诮缑嫔峡匆姷囊磺小?它們有一部分是我們UI定死的蜕该,也就是不會(huì)根據(jù)數(shù)據(jù)來更新顯示的犁柜,
比如一些Logo圖片啊,這里有個(gè)按鈕啊堂淡,那里有個(gè)輸入框啊馋缅,一些顯示特定內(nèi)容的label啊等等;
有一部分是會(huì)根據(jù)數(shù)據(jù)來顯示內(nèi)容的绢淀,比如tableView來顯示好友列表啊萤悴,
這個(gè)tableView的顯示內(nèi)容肯定是根據(jù)數(shù)據(jù)來顯示的。
我們使用MVC解決問題的時(shí)候皆的,通常是解決這些根據(jù)數(shù)據(jù)來顯示內(nèi)容的視圖覆履。
Controller(控制器):是應(yīng)用程序中處理用戶交互的部分。
通撤驯。控制器負(fù)責(zé)從視圖讀取數(shù)據(jù)硝全,控制用戶輸入,并向模型發(fā)送數(shù)據(jù)楞抡。
Controller是MVC中的數(shù)據(jù)和視圖的協(xié)調(diào)者伟众,也就是在Controller里面把Model的數(shù)據(jù)賦值給View來顯示
(或者是View接收用戶輸入的數(shù)據(jù)然后由Controller把這些數(shù)據(jù)傳給Model來保存到本地或者上傳到
服務(wù)器)。
綜合以上內(nèi)容拌倍,實(shí)際上你應(yīng)該可以通過面向?qū)ο蟮幕舅枷雭硗茖?dǎo)出controller出現(xiàn)的原因:我們所有的App都是界面和數(shù)據(jù)的交互赂鲤,所以需要類來進(jìn)行界面的繪制,于是出現(xiàn)了View柱恤,需要類來管理數(shù)據(jù)于是出現(xiàn)了Model数初。我們?cè)O(shè)計(jì)的View應(yīng)該能顯示任意的內(nèi)容比如頁面中顯示的文字應(yīng)該是任意的而不只是某個(gè)特定Model的內(nèi)容,所以我們不應(yīng)該在View的實(shí)現(xiàn)中去寫和Model相關(guān)的任何代碼梗顺,如果這樣做了泡孩,那么View的可擴(kuò)展性就相當(dāng)?shù)土恕6鳰odel只是負(fù)責(zé)處理數(shù)據(jù)的寺谤,它根本不知道數(shù)據(jù)到時(shí)候會(huì)拿去干啥仑鸥,可能拿去作為算法噼里啪啦去了,可能拿去顯示給用戶了变屁,它既然無法接收用戶的交互眼俊,它就不應(yīng)該去管和視圖相關(guān)的任何信息,所以Model中不應(yīng)該寫任何View相關(guān)代碼粟关。然而我們的數(shù)據(jù)和界面應(yīng)該同步疮胖,也就是一定要有個(gè)地方要把Model的數(shù)據(jù)賦值給View,而Model內(nèi)部和View的內(nèi)部都不可能去寫這樣的代碼闷板,所以只能新創(chuàng)造一個(gè)類出來了澎灸,取名為Controller。
3.下面來看這張圖
斯坦福大學(xué)公開課上的這幅圖來說明遮晚,這可以說是最經(jīng)典和最規(guī)范的MVC標(biāo)準(zhǔn)
在實(shí)際項(xiàng)目中也可以view持有controller饭聚,但是是weak持有
view和controller之間交互忌警,通過target-action來交互的,action是發(fā)生在view上秒梳,把a(bǔ)ction直接賦值給target法绵,就是controller
v 對(duì)c的另一個(gè)傳值就是,協(xié)議-委托
m 就是數(shù)據(jù)管理者酪碘,直接和數(shù)據(jù)庫打交道朋譬,請(qǐng)求數(shù)據(jù)數(shù)據(jù),
C才應(yīng)該用請(qǐng)求下來的數(shù)據(jù)重新賦值給V兴垦,現(xiàn)在的問題是徙赢,C如何知道網(wǎng)絡(luò)請(qǐng)求結(jié)束了字柠?
只要值發(fā)生改變,就直接變化view狡赐,這邊通過kvo或者Notification就直接傳給controoler了窑业,這邊就是整個(gè)mvc的理解
在mvc的年代,數(shù)據(jù)很簡單枕屉,沒有那么復(fù)雜常柄,數(shù)據(jù)解析很可能一步就解決了
但是現(xiàn)在app數(shù)據(jù)很復(fù)雜,現(xiàn)在就有“數(shù)據(jù)解析”這一步了搀擂,按照這個(gè)數(shù)據(jù)解析放在controller中西潘,數(shù)據(jù)解析就變的臃腫了,所有mvvm哨颂,
為啥不是mvcm呢因?yàn)閏的存在感降低喷市,就變成了mvvm模式
這張圖把MVC分為三個(gè)獨(dú)立的區(qū)域,并且中間用了一些線來隔開威恼。很有意思的設(shè)計(jì)东抹,因?yàn)檫@些線似乎出現(xiàn)在了駕校科目一的內(nèi)容中沃测,你瞧C和V以及C和M之間的白線缭黔,一部分是虛線一部分是實(shí)線對(duì)吧,這就表明了引用關(guān)系:C可以直接引用V和M蒂破,而V和M不能直接引用C馏谨,至少你不能顯式的在V和M的代碼中去寫和C相關(guān)的任何代碼,而V和M之間則是雙黃線附迷,沒錯(cuò)惧互,它們倆誰也不能引用誰,你既不能在M里面寫V喇伯,也不能在V里面寫M喊儡。哦,上面的描述有點(diǎn)小小的問題稻据,你不是“不能”這樣寫艾猜,而是“不應(yīng)該”這樣寫,沒人能阻止你在寫代碼的時(shí)候在一個(gè)M里面去寫V捻悯,但是一旦你這樣做了匆赃,那么你就違背了MVC的規(guī)范,你就不是在使用MVC了今缚,所以這算是MVC的一個(gè)必要條件:使用MVC –> M里面沒有V的代碼算柳。所以M里面沒有V的代碼就是使用MVC的必要條件。
View和Controller的交互
按鈕點(diǎn)擊事件姓言,是View來接收的瞬项,但是處理這個(gè)事件的應(yīng)該是Controller蔗蹋,所以View把這個(gè)事件傳遞給了Controller,如何傳遞的呢囱淋,見圖纸颜,看到View上面的action沒有,這就是事件绎橘,看到Controller上面的target沒有,這就是靶子唠倦,View究竟要把事件傳遞給誰称鳞,它被規(guī)定了傳遞給靶子,Controller實(shí)際上就是靶子稠鼻。只是View只負(fù)責(zé)傳遞事件冈止,不負(fù)責(zé)關(guān)心靶子是誰。就像你是一個(gè)負(fù)責(zé)運(yùn)貨的少年候齿,你唯一知道的是你要把貨(action)交給上頭(開發(fā)者)告訴你的那個(gè)收貨的人(target)熙暴,至于那個(gè)收貨的人是警察還是怪獸,你都不需要關(guān)心慌盯。這是V和C的一種交互方式周霉,叫做target-action。所以你看亚皂,這張圖簡直就是神來之筆俱箱,旁邊還栩栩如生的畫出了V對(duì)C的另一種傳值:協(xié)議-委托。委托有兩種:代理和數(shù)據(jù)源灭必。什么是代理狞谱,就是專門處理should、will禁漓、did事件的委托跟衅,什么是數(shù)據(jù)源,就是專門處理data播歼、count等等的委托伶跷。
Model和Controller的交互
M是干嘛的?上面說了秘狞,M就是數(shù)據(jù)管理者撩穿,你可以理解為它直接和數(shù)據(jù)庫打交道。這里的數(shù)據(jù)庫可能是本地的谒撼,也可能是服務(wù)器上的食寡,M會(huì)從數(shù)據(jù)庫獲取數(shù)據(jù),也可能把數(shù)據(jù)上傳給數(shù)據(jù)庫廓潜。M也將提供屬性或者接口來供C訪問其持有的數(shù)據(jù)抵皱。我們就拿一個(gè)簡單的需求作為例子善榛,假如我想在一個(gè)模塊中顯示一段文字,這段文字是從網(wǎng)上獲取下來的呻畸。
那么使用MVC的話移盆,在C中肯定需要一個(gè)UILabel(V)作為屬性來顯示這段文字,而這段文字由誰來獲取呢伤为,肯定是由M來獲取了咒循。而獲取的地方在哪里呢?通常在C的生命周期里面绞愚,所以往往是在C的一個(gè)生命周期方法比如viewDidLoad里面調(diào)用M獲取數(shù)據(jù)的方法來獲取數(shù)據(jù)⌒鸬椋現(xiàn)在問題來了,M獲取數(shù)據(jù)的方法是異步的網(wǎng)絡(luò)請(qǐng)求位衩,網(wǎng)絡(luò)請(qǐng)求結(jié)束后裆蒸,C才應(yīng)該用請(qǐng)求下來的數(shù)據(jù)重新賦值給V,現(xiàn)在的問題是糖驴,C如何知道網(wǎng)絡(luò)請(qǐng)求結(jié)束了僚祷?
這里我們一定要換一種角度去思考,我們進(jìn)一步考慮M和V之間的關(guān)系:它們應(yīng)該是一種同步的關(guān)系贮缕,也就是辙谜,不管任何時(shí)刻,只要M的值發(fā)生改變感昼,V的顯示就應(yīng)該發(fā)生改變(顯示最新的M的內(nèi)容)筷弦。所以我們可以關(guān)注M的值改變,而不用關(guān)心M的網(wǎng)絡(luò)請(qǐng)求是否結(jié)束了抑诸。實(shí)際上C根本不知道M從哪去拿的數(shù)據(jù)烂琴,C的責(zé)任是負(fù)責(zé)把M最新的數(shù)據(jù)賦值給V。所以C應(yīng)該關(guān)注的事件是:M的值是否發(fā)生了變化蜕乡。
4.MVVM
MVVM的誕生
就像我們之前分析MVC是如何合理分配工作的一樣奸绷,我們需要數(shù)據(jù)所以有了M,我們需要界面所以有了V层玲,而我們需要找一個(gè)地方把M賦值給V來顯示号醉,所以有了C,然而我們忽略了一個(gè)很重要的操作:數(shù)據(jù)解析辛块。在MVC出生的年代畔派,手機(jī)APP的數(shù)據(jù)往往都比較簡單,沒有現(xiàn)在那么復(fù)雜润绵,所以那時(shí)的數(shù)據(jù)解析很可能一步就解決了线椰,所以既然有這樣一個(gè)問題要處理,而面向?qū)ο蟮乃枷刖褪怯妙惡蛯?duì)象來解決問題尘盼,顯然V和M早就被定義死了憨愉,它們都不應(yīng)該處理“解析數(shù)據(jù)”的問題烦绳,理所應(yīng)當(dāng)?shù)模敖馕鰯?shù)據(jù)”這個(gè)問題就交給C來完成了配紫。而現(xiàn)在的手機(jī)App功能越來越復(fù)雜径密,數(shù)據(jù)結(jié)構(gòu)也越來越復(fù)雜,所以數(shù)據(jù)解析也就沒那么簡單了躺孝。如果我們繼續(xù)按照MVC的設(shè)計(jì)思路享扔,將數(shù)據(jù)解析的部分放到了Controller里面,那么Controller就將變得相當(dāng)臃腫植袍。還有相當(dāng)重要的一點(diǎn):Controller被設(shè)計(jì)出來并不是處理數(shù)據(jù)解析的惧眠。1、管理自己的生命周期奋单;2、處理Controller之間的跳轉(zhuǎn)猫十;3览濒、實(shí)現(xiàn)Controller容器。這里面根本沒有“數(shù)據(jù)解析”這一項(xiàng)拖云,所以顯然贷笛,數(shù)據(jù)解析也不應(yīng)該由Controller來完成。那么我們的MVC中宙项,M乏苦、V、C都不應(yīng)該處理數(shù)據(jù)解析尤筐,那么由誰來呢汇荐?這個(gè)問題實(shí)際上在面向?qū)ο蟮臅r(shí)候相當(dāng)好回答:既然目前沒有類能夠處理這個(gè)問題,那么就創(chuàng)建一個(gè)新的類出來解決不就好了盆繁?所以我們聰明的開發(fā)者們就專門為數(shù)據(jù)解析創(chuàng)建出了一個(gè)新的類:ViewModel掀淘。這就是MVVM的誕生。
如何實(shí)現(xiàn)MVVM
搞清楚了MVVM為什么會(huì)出現(xiàn)油昂,將對(duì)于你理解如何實(shí)現(xiàn)MVVM有極大的幫助革娄。在我們開始著手實(shí)現(xiàn)MVVM之前,我先簡單提一下之前遺留的一個(gè)問題:為什么MVVM這個(gè)名字里面冕碟,沒有Controller的出現(xiàn)(為什么不叫MVCVM拦惋,C去哪了)。本來這個(gè)問題應(yīng)該在實(shí)現(xiàn)后再來解釋安寺,但是我們這里是教學(xué)厕妖,為了讓大家更好的明白我們接下來的思想,所以這里要提前解釋一下這個(gè)結(jié)論:Controller的存在感被完全的降低了挑庶。我們?cè)诖龝?huì)實(shí)現(xiàn)MVVM的時(shí)候你就能體會(huì)到了叹放,這里請(qǐng)先把這個(gè)結(jié)論印在腦海當(dāng)中:Controller的存在感被完全的降低了饰恕、Controller的存在感被完全的降低了、Controller的存在感被完全的降低了井仰。
好的埋嵌,我們終于要開始著手實(shí)現(xiàn)MVVM了。如果你已經(jīng)搞懂了MVC俱恶,那么用MVVM實(shí)現(xiàn)一個(gè)相同的功能將會(huì)變得非常簡單雹嗦。你只需要記住兩點(diǎn):1、Controller的存在感被完全的降低了合是;2了罪、VM的出現(xiàn)就是Controller存在感降低的原因。
Controller存在感降低的原因
在MVVM中聪全,Controller不再像MVC那樣直接持有Model了泊藕。想象Controller是一個(gè)Boss,數(shù)據(jù)是一堆文件(Model)难礼,如果現(xiàn)在是MVC娃圆,那么數(shù)據(jù)解析(比如整理文件)需要由Boss親自完成,然而實(shí)際上Boss需要的僅僅是整理好的文件而不是那一堆亂七八糟的整理前的文件蛾茉。所以Boss招聘了一個(gè)秘書讼呢,現(xiàn)在Boss就不再需要管理原始數(shù)據(jù)(整理之前的文件)了,他只需要去找秘書:你幫我把文件整理好后給我谦炬。那么這個(gè)秘書就首先去拿到文件(原始數(shù)據(jù))悦屏,然后進(jìn)行整理(數(shù)據(jù)解析),接下來把整理的結(jié)果給Boss键思。所以秘書就是VM了础爬,并且Controller(Boss)現(xiàn)在只需要直接持有VM而不需要再持有M了。如果再進(jìn)一步理解C吼鳞、VM幕帆、M之間的關(guān)系:因?yàn)镃ontroller只需要數(shù)據(jù)解析的結(jié)果而不關(guān)心過程,所以就相當(dāng)于VM把“如何解析Model”給封裝起來了赖条,C甚至根本就不需要知道M的存在就能把工作做好失乾,前提它需要持有一個(gè)VM。那么我們MVVM中的持有關(guān)系就是:C持有VM纬乍,VM持有M碱茁。這里有一個(gè)比較爭議的地方:C該不該持有M。我的答案是不該仿贬。為什么呢纽竣,因?yàn)镃持有M沒有任何意義。就算C直接拿到了M的數(shù)據(jù),它還是要去讓VM進(jìn)行數(shù)據(jù)解析蜓氨,而數(shù)據(jù)解析就需要M聋袋,那么直接讓VM持有M而C直接持有VM就足夠了。最后再分享一個(gè)我在實(shí)現(xiàn)MVVM中的一個(gè)技巧穴吹,也談不上是技巧吧幽勒,算是一種必要的思想:一旦在實(shí)現(xiàn)Controller的過程中遇到任何跟Model(或者數(shù)據(jù))相關(guān)的問題,就找VM要答案港令。這個(gè)思想待會(huì)我們會(huì)在實(shí)現(xiàn)代碼的時(shí)候用到啥容。