關(guān)于iOS的幾種設(shè)計(jì)模式

傳統(tǒng)模式下的開(kāi)發(fā)
MVC
MVVM
基于面向協(xié)議MVP的介紹
MVP實(shí)戰(zhàn)開(kāi)發(fā)
說(shuō)在前面:
相信就算你是個(gè)iOS新手也應(yīng)該聽(tīng)說(shuō)過(guò)MVC的,MVC是構(gòu)建iOS App的標(biāo)準(zhǔn)模板辙纬。隨著時(shí)間的推移仰楚,在iOS平臺(tái)上MVC也逐漸開(kāi)始面臨著越來(lái)越多的問(wèn)題,最近又開(kāi)始流行MVVM,MVVM使由MVC衍生而來(lái)沃粗,MVVM作為一種新的開(kāi)發(fā)模式和響應(yīng)式編程相結(jié)合用來(lái)解決一部分業(yè)務(wù)場(chǎng)景等挂绰,今天抛腕,我要介紹給大家的是一個(gè)新的方式來(lái)架構(gòu)你的App: Model-View-Protocol,暫時(shí)可以理解為是基于協(xié)議的一種設(shè)計(jì)規(guī)范,拿出你的流行語(yǔ)bingo card韩肝,因?yàn)槲覀兗磳⑦M(jìn)行一次范式轉(zhuǎn)變触菜。

一、軟件設(shè)計(jì)鼻祖MVC

1.1哀峻、MVC
第一次聽(tīng)到MVC這個(gè)名詞是在C#中,相信對(duì)于MVC大家都已經(jīng)很熟悉了,作為一種軟件設(shè)計(jì)模式,MVC這個(gè)概念已經(jīng)誕生好多年了涡相。

如果你已經(jīng)開(kāi)發(fā)一段時(shí)間的iOS應(yīng)用,你一定聽(tīng)說(shuō)過(guò)Model-View-Controller,在iOS開(kāi)發(fā)中Apple從一開(kāi)始就給我們引入這一理念,相信這個(gè)名詞大家都不陌生。

模型-視圖-控制器(Model-View-Controller,MVC)是Xerox PARC在20世紀(jì)80年代為編程語(yǔ)言Smalltalk-80發(fā)明的一種軟件設(shè)計(jì)模式,至今已廣泛應(yīng)用于用戶交互應(yīng)用程序中剩蟀。在iOS開(kāi)發(fā)中MVC的機(jī)制被使用的淋漓盡致,充分理解iOS的MVC模式,有助于我們程序的組織合理性催蝗。

Model-View-Controller

MVC
模型對(duì)象
模型對(duì)象封裝了應(yīng)用程序的數(shù)據(jù),并定義操控和處理該數(shù)據(jù)的邏輯和運(yùn)算。例如,模型對(duì)象可能是表示商品數(shù)據(jù)list育特。用戶在視圖層中所進(jìn)行的創(chuàng)建或修改數(shù)據(jù)的操作,通過(guò)控制器對(duì)象傳達(dá)出去,最終會(huì)創(chuàng)建或更新模型對(duì)象丙号。模型對(duì)象更改時(shí)(例如通過(guò)網(wǎng)絡(luò)連接接收到新數(shù)據(jù)),它通知控制器對(duì)象,控制器對(duì)象更新相應(yīng)的視圖對(duì)象。

視圖對(duì)象
視圖對(duì)象是應(yīng)用程序中用戶可以看見(jiàn)的對(duì)象缰冤。視圖對(duì)象知道如何將自己繪制出來(lái),并可能對(duì)用戶的操作作出響應(yīng)犬缨。視圖對(duì)象的主要目的,就是顯示來(lái)自應(yīng)用程序模型對(duì)象的數(shù)據(jù),并使該數(shù)據(jù)可被編輯。盡管如此,在 MVC 應(yīng)用程序中,視圖對(duì)象通常與模型對(duì)象分離棉浸。

在iOS應(yīng)用程序開(kāi)發(fā)中,所有的控件怀薛、窗口等都繼承自 UIView,對(duì)應(yīng)MVC中的V。UIView及其子類主要負(fù)責(zé)UI的實(shí)現(xiàn),而UIView所產(chǎn)生的事件都可以采用委托的方式,交給UIViewController實(shí)現(xiàn)迷郑。

控制器對(duì)象
在應(yīng)用程序的一個(gè)或多個(gè)視圖對(duì)象和一個(gè)或多個(gè)模型對(duì)象之間,控制器對(duì)象充當(dāng)媒介枝恋〈淳螅控制器對(duì)象因此是同步管道程序,通過(guò)它,視圖對(duì)象了解模型對(duì)象的更改,反之亦然,控制器主要負(fù)責(zé)數(shù)據(jù)的傳遞解耦等工作》俾担控制器對(duì)象還可以為應(yīng)用程序執(zhí)行設(shè)置和協(xié)調(diào)任務(wù),并管理其他對(duì)象的生命周期畦攘。

控制器對(duì)象解釋在視圖對(duì)象中進(jìn)行的用戶操作,并將新的或更改過(guò)的數(shù)據(jù)傳達(dá)給模型對(duì)象。模型對(duì)象更改時(shí),一個(gè)控制器對(duì)象會(huì)將新的模型數(shù)據(jù)傳達(dá)給視圖對(duì)象,以便視圖對(duì)象可以顯示它十电。

M和V永遠(yuǎn)不能相互通信,只能通過(guò)控制器傳遞知押。控制器可以直接與Model對(duì)話(讀寫調(diào)用Model),Model通過(guò)通知或者KVO機(jī)制與控制器間接通信鹃骂±驶玻控制器可以直接與View對(duì)話,通過(guò)outlet,直接操作View,outlet直接對(duì)應(yīng)到View中的控件,View通過(guò)action向控制器報(bào)告事件的發(fā)生(如用戶的點(diǎn)擊事件)≠寺控制器是View的直接數(shù)據(jù)源(數(shù)據(jù)很可能是控制器從Model中取得并經(jīng)過(guò)加工了)∮欣拢控制器是View的代理(delegate),以同步View與Controller象踊。

MVC是一個(gè)用來(lái)組織代碼的權(quán)威范式,也是構(gòu)建iOS App的標(biāo)準(zhǔn)模式。Apple甚至是這么說(shuō)的棚壁。在MVC下,所有的對(duì)象被歸類為一個(gè)model,一個(gè)view,或一個(gè)controller杯矩。Model持有數(shù)據(jù),View顯示與用戶交互的界面,而View Controller調(diào)解Model和View之間的交互。然而,隨著模塊的迭代我們?cè)絹?lái)越發(fā)現(xiàn)MVC自身存在著很多不足袖外。因此,MVVM從其他應(yīng)用而出,在 iOS中從此我們完全將業(yè)務(wù)邏輯加以區(qū)分并使用這套思想史隆。

MVC在現(xiàn)實(shí)應(yīng)用中的不足
在上圖中,view將用戶交互通知給控制器。view的控制器通過(guò)更新Model來(lái)反應(yīng)狀態(tài)的改變曼验。Model(通常使用Key-Value-Observation)通知控制器來(lái)更新他們負(fù)責(zé)的view泌射。大多數(shù)iOS應(yīng)用程序的代碼使用這種方式來(lái)組織。

愈發(fā)笨重的Controller
在傳統(tǒng)的app中模型數(shù)據(jù)一般都很簡(jiǎn)單,不涉及到復(fù)雜的業(yè)務(wù)數(shù)據(jù)邏輯處理,客戶端開(kāi)發(fā)受限于它自身運(yùn)行的的平臺(tái)終端,這一點(diǎn)注定使移動(dòng)端不像PC前端那樣能夠處理大量的復(fù)雜的業(yè)務(wù)場(chǎng)景鬓照。然而隨著移動(dòng)平臺(tái)的各種深入,我們不的不考慮這個(gè)問(wèn)題熔酷。傳統(tǒng)的Model數(shù)據(jù)大多來(lái)源于網(wǎng)絡(luò)數(shù)據(jù),拿到網(wǎng)絡(luò)數(shù)據(jù)后客戶端要做的事情就是將數(shù)據(jù)直接按照順序畫在界面上。隨著業(yè)務(wù)的越來(lái)越來(lái)的深入,我們依賴的service服務(wù)可能在大多時(shí)間無(wú)法第一時(shí)間滿足客戶端需要的數(shù)據(jù)需求,移動(dòng)端愈發(fā)的要自行處理一部分邏輯計(jì)算操作豺裆。這個(gè)時(shí)間一慣的做法是在控制器中處理,最終導(dǎo)致了控制器成了垃圾箱,越來(lái)越不可維護(hù)拒秘。

控制器Controller是app的“膠水代碼”:協(xié)調(diào)模型和視圖之間的所有交互〕舨拢控制器負(fù)責(zé)管理他們所擁有的視圖的視圖層次結(jié)構(gòu),還要響應(yīng)視圖的loading躺酒、appearing、disappearing等等,同時(shí)往往也會(huì)充滿我們不愿暴露的Model的模型邏輯以及不愿暴露給視圖的業(yè)務(wù)邏輯蔑歌。這引出了第一個(gè)關(guān)于MVC的問(wèn)題...

視圖view通常是UIKit控件(component,這里根據(jù)習(xí)慣譯為控件)或者編碼定義的UIKit控件的集合羹应。進(jìn)入.xib或者Storyboard會(huì)發(fā)現(xiàn)一個(gè)app、Button丐膝、Label都是由這些可視化的和可交互的控件組成量愧。你懂的钾菊。View不應(yīng)該直接引用Model,并且僅僅通過(guò)IBAction事件引用controller。業(yè)務(wù)邏輯很明顯不歸入view,視圖本身沒(méi)有任何業(yè)務(wù)偎肃。

厚重的View Controller由于大量的代碼被放進(jìn)viewcontroller,導(dǎo)致他們變的相當(dāng)臃腫煞烫。在iOS中有的view controller里綿延成千上萬(wàn)行代碼的事并不是前所未見(jiàn)的。這些超重app的突出情況包括:厚重的View Controller很難維護(hù)(由于其龐大的規(guī)模)累颂;包含幾十個(gè)屬性,使他們的狀態(tài)難以管理滞详;遵循許多協(xié)議(protocol),導(dǎo)致協(xié)議的響應(yīng)代碼和controller的邏輯代碼混淆在一起。

厚重的view controller很難測(cè)試,不管是手動(dòng)測(cè)試或是使用單元測(cè)試,因?yàn)橛刑嗫赡艿臓顟B(tài)紊馏。將代碼分解成更小的多個(gè)模塊通常是件好事料饥。

太過(guò)于輕量級(jí)的Model
太過(guò)于輕量級(jí)的Model,早期的Model層,其實(shí)就是如果數(shù)據(jù)有幾個(gè)屬性,就定義幾個(gè)屬性,ARC普及以后我們?cè)贛odel層的實(shí)現(xiàn)文件中基本上看不到代碼( 無(wú)需再手動(dòng)管理釋放變量,Model既沒(méi)有復(fù)雜的業(yè)務(wù)處理,也沒(méi)有對(duì)象的構(gòu)造,基本上.m文件中的代碼普遍是空的);同時(shí)與控制器的代碼越來(lái)厚重形成強(qiáng)烈的反差,這一度讓人不禁對(duì)現(xiàn)有的開(kāi)發(fā)設(shè)計(jì)構(gòu)思有所懷疑朱监。

遺失的網(wǎng)絡(luò)邏輯
蘋果使用的MVC的定義是這么說(shuō)的:所有的對(duì)象都可以被歸類為一個(gè)Model,一個(gè)view,或是一個(gè)控制器岸啡。就這些。那么把網(wǎng)絡(luò)代碼放哪里赫编?和一個(gè)API通信的代碼應(yīng)該放在哪兒巡蘸?
你可能試著把它放在Model對(duì)象里,但是也會(huì)很棘手,因?yàn)榫W(wǎng)絡(luò)調(diào)用應(yīng)該使用異步,這樣如果一個(gè)網(wǎng)絡(luò)請(qǐng)求比持有它的Model生命周期更長(zhǎng),事情將變的復(fù)雜。顯然也不應(yīng)該把網(wǎng)絡(luò)代碼放在view里,因此只剩下控制器了擂送。這同樣是個(gè)壞主意,因?yàn)檫@加劇了厚重控制器的問(wèn)題悦荒。

那么應(yīng)該放在那里呢?顯然MVC的3大組件根本沒(méi)有適合放這些代碼的地方嘹吨。

較差的可測(cè)試性
MVC的另一個(gè)大問(wèn)題是,它不鼓勵(lì)開(kāi)發(fā)人員編寫單元測(cè)試搬味。由于控制器混合了視圖處理邏輯和業(yè)務(wù)邏輯,分離這些成分的單元測(cè)試成了一個(gè)艱巨的任務(wù)。大多數(shù)人選擇忽略這個(gè)任務(wù),那就是不做任何測(cè)試蟀拷。

上文提到了控制器可以管理視圖的層次結(jié)構(gòu)碰纬;控制器有一個(gè)“view”屬性,并且可以通過(guò)IBOutlet訪問(wèn)視圖的任何子視圖。當(dāng)有很多outlet時(shí)這樣做不易于擴(kuò)展,在某種意義上,最好不要使用子視圖控制器(child view controller)來(lái)幫助管理子視圖匹厘。

在這里有多個(gè)模糊的標(biāo)準(zhǔn),似乎沒(méi)有人能完全達(dá)成一致嘀趟。貌似無(wú)論如何,view和對(duì)應(yīng)的controller都緊緊的耦合在一起,總之,還是會(huì)把它們當(dāng)成一個(gè)組件來(lái)對(duì)待。Apple提供的這個(gè)組件一度以來(lái)在某種程度誤導(dǎo)了大多初學(xué)者,初學(xué)者將所有的視圖全部拖到xib中,連接大量的IBoutLet輸出口屬性,都是一些列問(wèn)題愈诚。

二她按、大劍之初MVVM
在經(jīng)歷了一大堆吐槽之后,誕生了MVVM(一個(gè)高大尚牛逼哄哄的名詞,從此又多了一種人,你懂MVVM ?如果你的回答是否,瞬間被鄙視一把)炕柔。

新思維

其實(shí)MVVM據(jù)說(shuō)最早在微軟的.NET平臺(tái)中出現(xiàn)過(guò)(具體什么背景,什么原因就不一一介紹了,還是要感謝偉大的.NET平臺(tái)工程師,造劍不如造經(jīng),世間萬(wàn)道皆不離其宗),無(wú)論是是MVVM還是MVC我們無(wú)需堅(jiān)持反對(duì)或者迷戀于它酌泰。在MVVM中他的設(shè)計(jì)思路和MVC很像。它正式規(guī)范了視圖和控制器緊耦合的性質(zhì),并引入新的組件匕累。

Model-View-ViewModel
在理想的世界里,MVC也許工作的很好陵刹。然而,我們生活在真實(shí)的世界。既然我們已經(jīng)詳細(xì)說(shuō)明了MVC在典型場(chǎng)景中的問(wèn)題,那讓我們看一看一個(gè)可供替換的選擇:Model-View-ViewModel欢嘿。

在MVVM里,view和view controller正式聯(lián)系在一起,我們把它們視為一個(gè)組件衰琐。視圖view仍然不能直接引用模型Model,當(dāng)然controller也不能也糊。相反,他們引用視圖模型view Model。

view Model是一個(gè)放置用戶輸入驗(yàn)證邏輯,視圖顯示邏輯,發(fā)起網(wǎng)絡(luò)請(qǐng)求和其他各種各樣的代碼的極好的地方羡宙。有一件事情不應(yīng)歸入view Model,那就是任何視圖本身的引用狸剃。view Model的概念同時(shí)適用于于iOS和OS X。(換句話說(shuō),不要在view Model中使用 #import UIKit.h)

由于展示邏輯(presentation logic)放在了view Model中(比如Model的值映射到一個(gè)格式化的字符串),視圖控制器本身就會(huì)不再臃腫狗热。當(dāng)你開(kāi)始使用MVVM的最好方式是,可以先將一小部分邏輯放入視圖模型,然后當(dāng)你逐漸習(xí)慣于使用這個(gè)范式的時(shí)候再遷移更多的邏輯到視圖模型中钞馁。

以我的經(jīng)驗(yàn),使用MVVM會(huì)輕微的增加代碼量,但總體上減少了代碼的復(fù)雜性。這是一個(gè)劃算的交易匿刮。

回過(guò)頭再來(lái)看MVVM的圖示,你會(huì)注意到我使用了模糊的動(dòng)詞“notify”和“update”,而沒(méi)有詳細(xì)說(shuō)明該怎么做僧凰。你可以使用KVO,就像MVC那樣,但這很快就會(huì)變得難以管理。事實(shí)上,使用ReactiveCocoa會(huì)是更好的方式來(lái)組織各個(gè)部分熟丸。

關(guān)于怎么結(jié)合ReactiveCocoa來(lái)使用MVVM的信息,可以閱讀開(kāi)源app训措。你也可以閱讀我的關(guān)于ReactiveCocoa和MVVM的書。

關(guān)于MVVM的具體細(xì)節(jié)此處就不多詳細(xì)介紹,此處重在做對(duì)比分析,如果了解的可以去了解相關(guān)資料光羞。

三隙弛、基于面向協(xié)議MVP的介紹

曾經(jīng)有無(wú)數(shù)個(gè)人總喜歡問(wèn)我你們的iOS采用什么樣的架構(gòu),其實(shí)每次被問(wèn)到這樣的問(wèn)題,不是瞬間被萌了,就是想自己?jiǎn)栕约篿OS也有架構(gòu)?狞山?

上文提到了MVC、MVVM,真實(shí)的業(yè)務(wù)場(chǎng)景中,如果場(chǎng)景的邏輯異常復(fù)雜,在反復(fù)的迭代中仍會(huì)出現(xiàn)各式各樣的問(wèn)題叉寂。真對(duì)MVVM我個(gè)人理解主要是將原來(lái)Controller中處理數(shù)據(jù)邏輯的代碼統(tǒng)一歸到一個(gè)新的class(viewModel)中去,更甚之網(wǎng)絡(luò)請(qǐng)求等工作全部從Controller移到viewModel萍启。剛一開(kāi)始總覺(jué)的怪怪的。現(xiàn)階段客戶端開(kāi)發(fā)越來(lái)越進(jìn)入一個(gè)2.0的階段,早期的app功能都相對(duì)比較簡(jiǎn)單,無(wú)論是從界面還是從業(yè)務(wù)邏輯上給人的感覺(jué)都是簡(jiǎn)潔實(shí)用,這中間包括UI的設(shè)計(jì)屏鳍、功能的設(shè)計(jì)勘纯、產(chǎn)品的設(shè)計(jì)定位等。隨著行業(yè)的深入,用戶的過(guò)渡依賴移動(dòng)端最終導(dǎo)致業(yè)各式各樣的業(yè)務(wù)更加依賴客戶端,這就導(dǎo)致客戶端的開(kāi)發(fā)不得不向PC端靠齊,在版本的反復(fù)迭代中業(yè)務(wù)場(chǎng)景變的愈發(fā)不盡人意,仿佛又回到了軟件設(shè)計(jì)的早期钓瞭。

在傳統(tǒng)軟件領(lǐng)域,從MVC的誕生主要是為了解決軟件界面的行為的分離,在復(fù)雜的業(yè)務(wù)場(chǎng)景內(nèi)會(huì)進(jìn)一步區(qū)分業(yè)務(wù)邏輯場(chǎng)景的分離,這些手段的最終的目的都是盡最大限度的降低整個(gè)場(chǎng)景的藕合度,使其達(dá)到分離的目的,模塊與模塊最終得到獨(dú)立,將整個(gè)場(chǎng)景化整為零,最終使每個(gè)模塊在一個(gè)零上工作,這對(duì)于無(wú)論是軟件的開(kāi)發(fā)還是后續(xù)的維護(hù)驳遵、以及使用普遍遵循這個(gè)原則,現(xiàn)有的模式大概產(chǎn)生了相關(guān)的類似架構(gòu)。

傳統(tǒng)web架構(gòu)里面是這樣解決的 :

service
web段以及其他業(yè)務(wù)層負(fù)責(zé)從接口層獲取數(shù)據(jù)并執(zhí)行自己的邏輯
service層為外部提供接口
DTO從負(fù)責(zé)從DB鏈接并進(jìn)行數(shù)據(jù)讀寫操作
DB層(物理機(jī)負(fù)責(zé)數(shù)據(jù)存儲(chǔ))
現(xiàn)有客戶度一度采用下面的模式:

MVC
客戶端通過(guò)service拿到j(luò)son 數(shù)據(jù),然后通過(guò)MVC的結(jié)構(gòu)展示到UI界面上,在iOS中一直流行MVC的開(kāi)發(fā)模式,通過(guò)與傳統(tǒng)開(kāi)發(fā)模式對(duì)比可以發(fā)現(xiàn),其實(shí)
service層-客戶端交互與服務(wù)端service服務(wù)滿足外部業(yè)務(wù)場(chǎng)景無(wú)非是兩個(gè)互逆的過(guò)程(一個(gè)輸出層,一個(gè)輸入層,都是為了更好的滿足的下一步的業(yè)務(wù)需求,一個(gè)是將原始數(shù)據(jù)邏輯話,一個(gè)是將獲得邏輯數(shù)據(jù)存檔并且展示到用戶面前)山涡。service層根據(jù)具體的業(yè)務(wù)場(chǎng)景提供對(duì)應(yīng)的數(shù)據(jù)服務(wù),service根據(jù)不同的業(yè)務(wù)場(chǎng)景通過(guò)DTO層拿到對(duì)應(yīng)
的數(shù)據(jù)然后組織好數(shù)據(jù)提供給外界(service 層負(fù)責(zé)將原始物理數(shù)據(jù)轉(zhuǎn)換成對(duì)應(yīng)的邏輯數(shù)據(jù)提供給外界)堤结。

相反,客戶端通過(guò)網(wǎng)絡(luò)層拿到對(duì)應(yīng)的網(wǎng)絡(luò)數(shù)據(jù)繪制到對(duì)應(yīng)的View上,但是實(shí)際的開(kāi)發(fā)過(guò)程中,網(wǎng)絡(luò)數(shù)據(jù)與真實(shí)客戶端使用場(chǎng)景也是有一定的差距,MVVM層將對(duì)應(yīng)的
一部分邏輯處理移植到了ViewModel中,這并沒(méi)有從根本上解決問(wèn)題,無(wú)非是將代碼做了一份對(duì)應(yīng)的copy轉(zhuǎn)移,并沒(méi)有從根本上達(dá)到邏輯分層的概念。相反MVP模
式恰好解決了這一難題,MVP模式衍生于傳統(tǒng)service架構(gòu),針對(duì)不同的業(yè)務(wù)場(chǎng)景圖供對(duì)應(yīng)的匹配的抽象service服務(wù),客戶端拿到網(wǎng)絡(luò)數(shù)據(jù)后未達(dá)到指定的目的,
為滿足相同抽象邏輯的業(yè)務(wù)場(chǎng)景,在客戶端網(wǎng)絡(luò)層與Model層之間加一協(xié)議層,Model層實(shí)現(xiàn)整個(gè)協(xié)議層,之后在基于MVC的結(jié)構(gòu)下將一概相同層次的
業(yè)務(wù)場(chǎng)景繪制解釋到對(duì)應(yīng)的View上鸭丛。

MVP
M : 邏輯Model層
V : 視圖層
P : protocol協(xié)議層
Model層類似于MVVM的ViewModel,主要負(fù)責(zé)存儲(chǔ)抽象邏輯數(shù)據(jù),另外Model層主還有部分工作實(shí)現(xiàn)對(duì)應(yīng)的協(xié)議層協(xié)議,提供協(xié)議對(duì)應(yīng)的各種屬性以及服務(wù)竞穷。Model經(jīng)過(guò)協(xié)議層抽象約束,最后Model被抽象成具有統(tǒng)一抽象邏輯的業(yè)務(wù)場(chǎng)景,最終Model層在講數(shù)據(jù)交付整個(gè)MVC結(jié)構(gòu)繪制展示的時(shí)間,我們可以按照同一套抽象的邏輯標(biāo)準(zhǔn)去執(zhí)行。

在傳統(tǒng)的web層面,為了滿足各式各樣的業(yè)務(wù)邏輯場(chǎng)景服務(wù),最紅我們實(shí)現(xiàn)軟件羅杰的層次的分離,誕生了service服務(wù)這個(gè)概念(service就類似一個(gè)標(biāo)準(zhǔn)尺寸的水龍頭出口,只要對(duì)應(yīng)的水龍頭都按照這樣的規(guī)則來(lái)生產(chǎn),service就能夠滿足格式各樣的業(yè)務(wù)場(chǎng)景,極大的解決的傳統(tǒng)軟件服務(wù)業(yè)務(wù)場(chǎng)景層次的一系列難題)鳞溉;相同的原理在客戶端同樣可以使用,為了滿足客戶端MVC結(jié)構(gòu)層里面的穩(wěn)定,避免各式各樣的業(yè)務(wù)場(chǎng)景迭代插入不同的邏輯,避免最終軟件危機(jī)的產(chǎn)生,我們采用追加協(xié)議層的模式來(lái)滿足這一目的瘾带。

遍觀整個(gè)軟件開(kāi)發(fā),從早期的軟件開(kāi)發(fā),到后來(lái)軟件生產(chǎn)管理的危機(jī),軟件開(kāi)發(fā)模式一步步的確立,軟件行業(yè)的每個(gè)階段都是一個(gè)里程碑。這世間沒(méi)有相對(duì)完美獨(dú)到的設(shè)計(jì)法則,但是亙古不變永遠(yuǎn)只有一個(gè)那就是軟件的開(kāi)發(fā)更佳面相生產(chǎn)化熟菲、規(guī)范化看政、更加的利于可維護(hù)化朴恳。一直以來(lái)我本人并不特別的注重軟件的設(shè)計(jì)一定、必須按照某種規(guī)則來(lái)做,畢竟不同的人允蚣、不同的業(yè)務(wù)場(chǎng)景于颖、不同的工程師總有不同的實(shí)際境況,站在一個(gè)開(kāi)發(fā)工程師的角度來(lái)說(shuō)我并不固執(zhí)于都按照固定的規(guī)則來(lái)(比如說(shuō)你必須按照某個(gè)模式來(lái)做,必須用MVVM來(lái)做;必須用ReactCocoa信號(hào)型機(jī)制來(lái)做...)厉萝。相反我個(gè)人認(rèn)為太過(guò)于固執(zhí)只不過(guò)某些人的一廂情愿的罷了恍飘。相反我覺(jué)得因地制宜、應(yīng)運(yùn)而生豈不更加快哉,設(shè)計(jì)不拘于模式,更多時(shí)間更是不局限于思考谴垫。無(wú)論是MVVM章母、MVP哪一個(gè)不是脫胎于MVC,這個(gè)世間萬(wàn)變不離其宗,萬(wàn)千功法始終都離不開(kāi)一部最終的母經(jīng)。

四翩剪、MVP實(shí)戰(zhàn)開(kāi)發(fā)

說(shuō)了這么多,下面上實(shí)戰(zhàn)例子乳怎。

大概描述一下業(yè)務(wù)場(chǎng)景,作為電商app,我們希望在原生的基礎(chǔ)上開(kāi)發(fā)一套定制的可控、可配前弯、可維護(hù)的通用型原生模版(至于說(shuō)的這么靈活 有多么的好,為啥不用H5蚪缀、ReactNative,這個(gè)問(wèn)題不要來(lái)問(wèn)我,產(chǎn)品狗們讓做原生,程序員只能執(zhí)行)。大概是這樣一個(gè)場(chǎng)景,可以配置的樓層樣式多達(dá)十幾種(至少目前已經(jīng)有十幾種,以后可能會(huì)更多)恕出;每種可配置樓層樣式是多元的,外觀長(zhǎng)相不一,數(shù)據(jù)格式也不盡相同但有部分類同询枚;要求后臺(tái)CMS配置界面配置法則有共同相似之處;要求每種樣式樓層處理事件記憶跳轉(zhuǎn)不盡相同浙巫;最可恨的頁(yè)面已經(jīng)很長(zhǎng)了以后會(huì)源源不斷加入新的模版金蜀。
考慮到長(zhǎng)遠(yuǎn),這樣的復(fù)雜樓層,如果仍舊按照傳統(tǒng)的模式來(lái)做,問(wèn)題會(huì)很多,數(shù)據(jù)無(wú)法統(tǒng)一、無(wú)法統(tǒng)一繪制的畴、無(wú)法統(tǒng)一處理渊抄。具體場(chǎng)景相信大家應(yīng)該理解了。

上設(shè)計(jì)思路

潛在問(wèn)題

server段需要針對(duì)不同的樓層場(chǎng)景下發(fā)不同的數(shù)據(jù) 數(shù)據(jù)結(jié)構(gòu)不盡相同
模版樓層樣式不盡相同 可能對(duì)應(yīng)多種View
多種View與多種數(shù)據(jù)結(jié)構(gòu)的解釋解耦問(wèn)題
多種業(yè)務(wù)場(chǎng)景用戶操作邏輯處理問(wèn)題
樓層過(guò)于復(fù)雜 Controller代碼大爆炸
邏輯建模分析

暫時(shí)可以將每種模版樓層的整體數(shù)據(jù)作為一個(gè)容器Modle,主要負(fù)責(zé)該樓層的整體數(shù)據(jù)調(diào)度
將每種樓層公有的屬性以及內(nèi)容抽象出來(lái)放入一個(gè)容器父類Container,然后將不同模版特有的屬性放在子模版派生Model中,作為派生屬性
對(duì)準(zhǔn)一個(gè)容器類,我可以將每種容器Model的使用法則抽象總結(jié)歸納(1丧裁、樓層是否有Header,是否要吸頂护桦;2、該樓層具體要由什么樣的View模版去繪制; 3煎娇、樓層內(nèi)容是繪制在單個(gè)section單個(gè)cell中還是繪制在多行上; 4二庵、每個(gè)樓層的元素點(diǎn)擊跳轉(zhuǎn)處理等). 我們將容器這塊作為一個(gè)數(shù)據(jù)源概念最終抽象出一套可供外界獲取數(shù)據(jù)的Interface(Protocol)
當(dāng)我們拿到樓層數(shù)據(jù)后在父容器基礎(chǔ)上做子樓層的派生,我們要求派生容器去實(shí)現(xiàn)上述父容器的Protocol,通過(guò)協(xié)議我們可以知道具體的繪制的目標(biāo),以及要繪制的元素個(gè)數(shù)等,最終達(dá)到一個(gè)目的,
將每個(gè)樓層的數(shù)據(jù)裝配在我們定義好的一個(gè)適配器容器內(nèi),然后通過(guò)協(xié)議給外界提供一套統(tǒng)一的操作入口,之后我們才用統(tǒng)一的操作方式操作容器,最終實(shí)現(xiàn)一個(gè)容器對(duì)應(yīng)一個(gè)樓層。
Render 協(xié)議,在這個(gè)我們對(duì)準(zhǔn)每個(gè)要具體繪制到UI上的Model,我們統(tǒng)一讓其實(shí)現(xiàn)Render協(xié)議,通過(guò)適配器容器我們我們拿到具體要繪制的目標(biāo),
目標(biāo)繪制題都實(shí)現(xiàn)了Render協(xié)議,在Render協(xié)議我們可以拿到具體當(dāng)前Model將由哪個(gè)具體的Cell去呈遞缓呛。在每個(gè)繪制目標(biāo)題內(nèi)由Model決定
當(dāng)前內(nèi)容由什么樣式的cell模版去繪制眨猎。我們把所有的樓層數(shù)據(jù)處理邏輯壓在適配器容器內(nèi),再將Model的繪制目標(biāo)都交由Model自己決定。
實(shí)現(xiàn)上述目標(biāo)后,在ViewController層面,我們看到的只有一個(gè)實(shí)現(xiàn)了適配器協(xié)議的Model數(shù)組,在 table的繪制過(guò)程我們通過(guò)操作一個(gè)
id<適配器protocol> 類型的Model對(duì)象, 拿到這個(gè)具體的索引對(duì)應(yīng)的對(duì)象后,通過(guò)內(nèi)部已經(jīng)實(shí)現(xiàn)的協(xié)議我們很快的拿到下一個(gè)要繪制的目標(biāo)Model
然后再拿到具體的Cell模版的Identifier,然后從tableview中取到當(dāng)前Identifier對(duì)應(yīng)的cell模版,傳入數(shù)據(jù)最后返回一個(gè)cell强经。
這個(gè)地方我們先定義了一個(gè)適配容器協(xié)議,以及一個(gè)父容器類,我們將樓層公有屬性放在父類中,將派生容器子類(具體的樓層Model)實(shí)現(xiàn)一個(gè)抽象協(xié)議睡陪。
MVP模式的原則是,在service層提供一大堆不盡相同的業(yè)務(wù)場(chǎng)景之后,我們將這一系列數(shù)據(jù)全部抽象歸納兰迫,通過(guò)定制一套標(biāo)準(zhǔn)的protocol協(xié)議信殊,讓不同業(yè)務(wù)場(chǎng)景的都去實(shí)現(xiàn)這一協(xié)議,最終將數(shù)據(jù)全部裝配汁果、拼裝成一套具有相同調(diào)度規(guī)則的統(tǒng)一編制話數(shù)據(jù)Model涡拘,之后我們采用id的標(biāo)準(zhǔn)去操作數(shù)據(jù)。

MVP除了將數(shù)據(jù)邏輯完全鎮(zhèn)封的各自的Model,同時(shí)將那些Model需要繪制据德,哪些Model需要校驗(yàn), 哪些Model需要接受處理點(diǎn)擊Action這些邏輯全部由Model自己來(lái)決定鳄乏,Controller只作為一個(gè)粘合性的模版,Controller只處理一批具有共性的范型類數(shù)據(jù)棘利,至于具體的操作操作毫不關(guān)心橱野。

MVP面相的更多的是在MVC上層與service之間追加了一層協(xié)議層,我們認(rèn)為通過(guò)協(xié)議層處理過(guò)的數(shù)據(jù)是暫時(shí)可以客戶端場(chǎng)景使用的數(shù)據(jù)善玫,在數(shù)據(jù)到達(dá)MVC我們針對(duì)數(shù)據(jù)進(jìn)行再加工水援、再構(gòu)造處理,這一切全部在容器內(nèi)操作茅郎。這一點(diǎn)完全與別的設(shè)計(jì)模式相反蜗元。

MVP并不是讓用戶在Model打上網(wǎng)絡(luò)請(qǐng)求操作、在Model層執(zhí)行[self.navigationController pushViewController:*]等這些操作系冗,其實(shí)相對(duì)大多人來(lái)說(shuō)對(duì)于部分對(duì)象生命周期長(zhǎng)短問(wèn)題還是很在乎奕扣,所以在處理TemplateActionProtocol協(xié)議的時(shí)間,MVP只是對(duì)準(zhǔn)Action做了一層抽象封裝掌敬。通過(guò)實(shí)現(xiàn)TemplateActionProtocol的Model會(huì)產(chǎn)生一個(gè)Action對(duì)象成畦,最終通過(guò)block的調(diào)用鏈傳回控制器中,我們?cè)诳刂破髦薪y(tǒng)一做handler處理。

MVP我們最初設(shè)計(jì)目的就是為了強(qiáng)調(diào)一個(gè)裝配概念涝开,如果發(fā)生了業(yè)務(wù)場(chǎng)景的追加,控制器我不會(huì)改動(dòng)其中的代碼,只需要將新數(shù)據(jù)追加成相同批次的ViewModel框仔,然后配置進(jìn)容器舀武,之后控制器不做任何修改就可以滿足需求了。通過(guò)具體的剝離离斩、抽取我們成功了的最大限度的剝離了控制器银舱,滿足了輕量級(jí)Controller這一概念。

MVP與傳統(tǒng)軟件相比,在設(shè)計(jì)這一點(diǎn)的時(shí)間我們完全借鑒了傳統(tǒng)軟件的思維模式跛梗,Java平臺(tái)的service設(shè)計(jì)模式寻馏、三層架構(gòu)這些設(shè)計(jì)規(guī)范都相對(duì)做了一些對(duì)比分析,最終得出了MVP這一理念核偿。

分析場(chǎng)景完畢,下面來(lái)分析一個(gè)模版的例子來(lái)說(shuō)命一切吧3锨贰!

Protocol 設(shè)計(jì)
Template 協(xié)議

-TemplateRenderProtocol //任何一個(gè)具體繪制到cell上的Model都需要實(shí)現(xiàn)該協(xié)議
-TemplateSorbRenderProtocol //樓層的header如果要吸頂需要使用該協(xié)議替代基本的Render協(xié)議
-TemplateActionProtocol //具有Action 操作的Model需要實(shí)現(xiàn),返回一個(gè)通用的Action對(duì)象
-TemplateCellProtocol //整個(gè)體系中所有的Cell統(tǒng)一實(shí)現(xiàn)該協(xié)議
TemplateValidationProtocol //- 處理一部分認(rèn)證校驗(yàn)數(shù)據(jù)
ViewController

樓層顯示統(tǒng)一交與Model層定制
VC中生成ActionModel,跳轉(zhuǎn)邏輯全部應(yīng)用于Action協(xié)議層,ViewController實(shí)現(xiàn)ActionManager 代理,作為回調(diào)處理
特定屬性處理邏輯放在分類內(nèi)
網(wǎng)絡(luò)層調(diào)用扔保持在ViewController,這一點(diǎn)與傳統(tǒng)保持相似,有利于結(jié)構(gòu)分明(優(yōu)于市面上的所謂MVVM)
//TemplateRenderProtocol.h
@protocol TemplateRenderProtocol @required

  • (NSString *)floorIdentifier;

@end

//TemplateSorbRenderProtocol.h
@protocol TemplateSorbRenderProtocol - (NSString *)headerFloorIdentifier;

  • (id )headerFloorModelAtIndex:(NSInteger)index;

@end

//TemplateActionProtocol.h
*/
@protocol TemplateActionProtocol @optional

  • (TemplateAction *)jumpFloorModelAtIndexPath:(NSIndexPath *)indexPath;

@end

//TemplateCellProtocol.h
@protocol TemplateBaseProtocol;

typedef void (^TapBlock) (NSIndexPath* index);

@protocol TemplateCellProtocol @optional

  • (CGSize)calculateSizeWithData:(id)data constrainedToSize:(CGSize)size;
  • (void)processData:(id )data;

  • (void)tapOnePlace:(TapBlock) block;

@end
Model設(shè)計(jì)
// TemplateContainerModel.h

/**

  • 容器概念
    */
    @protocol TemplateContainerProtocol @required
  • (NSInteger)numberOfChildModelsInContainer;

  • (id )childFloorModelAtIndex:(NSInteger)index;

@end

@class TemplateChannelModel;
@interface TemplateContainerModel : NSObject//netList
@property (nonatomic,strong) NSNumber *identityId;
@property (nonatomic,strong) NSString *pattern;
@property (nonatomic,strong) TemplateFHeaderModel *fheader;
@property (nonatomic,strong) NSArray *itemList;
@property (nonatomic,strong) TemplateJumpModel *jump;
@property (nonatomic,strong) TemplateMarginModel *margin;
//other add
@property (nonatomic,assign) TemplateChannelModel *channelModel;
@end
下面的就先引用一個(gè)具體的業(yè)務(wù)場(chǎng)景吧,頂部banner樓層,每個(gè)大的樓層都是一個(gè)容器Model,是繼承于父容器,并且會(huì)適當(dāng)重寫父類協(xié)議以及方法

//TemplateFloorFocusModel.h

//此處,banner是多個(gè)對(duì)象繪制成輪播的樣式,整體是繪制在同一個(gè)cell上的,所以TemplateFloorFocusModel首先是一個(gè)容器類,是具有數(shù)據(jù)源的
功能,但是他又是一個(gè)繪制目標(biāo)Model,TemplateFloorFocusModel實(shí)現(xiàn)了Render協(xié)議,就決定這個(gè)接下來(lái)會(huì)將TemplateFloorFocusModel繪制到UI界面上(如果此處的容器存儲(chǔ)的是一個(gè)section下的list形式,容器類就無(wú)需實(shí)現(xiàn)render協(xié)議,只需要將list 中的Model實(shí)現(xiàn)render協(xié)議即可)

@interface TemplateFloorFocusModel : TemplateContainerModel@property (nonatomic,assign) NSNumber *width;
@property (nonatomic,assign) NSNumber *height;

@end

//TemplateFloorFocusModel.m

@implementation TemplateFloorFocusModel

  • (NSDictionary *)mj_replacedKeyFromPropertyName
    {
    return @{
    @"itemList" : @"picList"
    };
    }

  • (NSDictionary *)mj_objectClassInArray
    {
    return @{
    @"itemList" : @"TemplatePicModel"
    };
    }

//pragma mark - TemplateContainerProtocol

  • (NSInteger)numberOfChildModelsInContainer
    {
    NSUInteger rows = 0;
    if (self.margin) rows++;
    if (self.itemList) rows++;
    return rows;
    }

//(如果此處的容器存儲(chǔ)的是一個(gè)section下的list形式,此處返回一個(gè)實(shí)現(xiàn)render協(xié)議的Model即可)

  • (id )childFloorModelAtIndex:(NSInteger)index
    {
    if ((self.margin)&&(index+1) == [self numberOfChildModelsInContainer])
    return self.margin; //最后一行
    return self;
    }

//pragma mark - TemplateActionProtocol

  • (TemplateAction *)jumpFloorModelAtIndexPath:(NSIndexPath *)indexPath
    {
    NSUInteger position = [indexPath indexAtPosition:0];
    if (position < self.itemList.count) {

      TemplatePicModel *picModel = self.itemList[position];
      TemplateJumpAction *action = [[TemplateJumpAction alloc] init];
      action.jumpToType = TemplateJumpToActivityM;
      action.jumpToUrl = picModel.jump.url;
      action.eventId = @"GeneralChannel_BannerPic";
      return action;
    

    }
    return nil;
    }

//pragma mark - TemplateRenderProtocol

  • (NSString *)floorIdentifier
    {
    return @"TemplateFocusCell";
    }
    View 設(shè)計(jì)
    View設(shè)計(jì)此處我們才用方式依舊是將Cell作為模版,將對(duì)應(yīng)的視圖邏輯統(tǒng)一放在一個(gè)UIViewSubView中, 之后在Cell中將View直接add到cell.ContentView上。

針對(duì)焦點(diǎn)圖cell TemplateFocusCell我們有一個(gè)TemplateFocusView來(lái)對(duì)應(yīng),下面看下代碼設(shè)計(jì)

TemplateFocusCell

// TemplateFocusCell
@interface TemplateFocusCell : UITableViewCell@end

@interface TemplateFocusCell (){
TemplateFocusView *_focusView;
}

@property (nonatomic,strong) id data;

@end
@implementation TemplateFocusCell

  • (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
    {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

    if (self) {
    _focusView = [[TemplateFocusView alloc] init];
    _focusView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.contentView addSubview:_focusView];
    }
    return self;
    }

  • (void)processData:(id )data
    {
    if([data isKindOfClass:[TemplateFloorFocusModel class]])
    {
    self.data = data;
    [_focusView processData:(id )data];
    }
    }

  • (CGSize)calculateSizeWithData:(id)data constrainedToSize:(CGSize)size
    {
    // id model = data;
    CGSize curSize = CGSizeMake(ScreenWidth, 110);
    return curSize;
    }
  • (void)tapOnePlace:(TapBlock) block
    {
    [_focusView setTapBlock:block];
    }
    TemplateFocusView

@interface TemplateFocusView : UIView@end

@interface TemplateFocusView (){
UIPageControl *_pageControl;
iCarousel *_scrollView;
}

@property (nonatomic,strong) TemplateFloorFocusModel *focusModel;
@end

@implementation TemplateFocusView

  • (instancetype)init
    {
    self = [super init];

    if (self)
    {
    _scrollView = [[iCarousel alloc] init];
    _scrollView.delegate = self;
    _scrollView.dataSource = self;
    _scrollView.type = iCarouselTypeLinear;
    _scrollView.pagingEnabled = YES;
    _scrollView.bounceDistance = 0.5;
    _scrollView.decelerationRate = 0.5;
    _scrollView.clipsToBounds = YES;
    _scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:_scrollView];

      _pageControl = [[UIPageControl alloc] init];
      _pageControl.translatesAutoresizingMaskIntoConstraints = NO;
      [self addSubview:_pageControl];
    
      [_scrollView mas_makeConstraints:^(MASConstraintMaker *make){
          make.edges.equalTo(self).insets(UIEdgeInsetsZero);
      }];
    
      [_pageControl mas_makeConstraints:^(MASConstraintMaker *make){
          make.bottom.mas_equalTo(@(5));
          make.centerX.equalTo(self);
      }];
    

    }
    return self;
    }

  • (CGSize)calculateSizeWithData:(id)data constrainedToSize:(CGSize)size
    {
    return size;
    }
  • (void)processData:(id )data
    {
    self.focusModel = (TemplateFloorFocusModel *)data;
    _pageControl.numberOfPages = self.focusModel.itemList.count;
    [_scrollView reloadData];

    [self layoutIfNeeded];
    }

//pragma mark -

  • (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
    {
    return _focusModel.itemList.count;
    }

  • (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
    {
    UIImageView *imageView = nil;
    if (!view) {
    imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenWidth/2)];
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    }else{
    imageView = (UIImageView *)view;
    }

    TemplatePicModel *model = self.focusModel.itemList[index];
    [imageView setImageWithURL:[NSURL URLWithString:model.img]];
    return imageView;
    }

  • (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value
    {
    if (option == iCarouselOptionWrap)
    {
    return YES;
    }
    return value;
    }

  • (void)carouselDidEndScrollingAnimation:(iCarousel *)carousel
    {
    NSInteger index = _scrollView.scrollOffset;

    [_pageControl setCurrentPage:index];
    }

  • (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index
    {
    NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:index];
    if (_tapBlock) {
    _tapBlock(indexPath);
    }
    }
    從View層可以看到,我們?nèi)耘f遵循以往的模式,將cell高度的計(jì)算,最終放在View中來(lái)完成(此處我們并沒(méi)有Model化,而是仍舊遵循大家的習(xí)慣,具體的高度根據(jù)具體的視圖場(chǎng)景來(lái)控制),看到此處的計(jì)算高度的方法,接下來(lái)的問(wèn)題就不多說(shuō)了....

Controller 設(shè)計(jì)
在做完以上的一些列的邏輯化抽象工作以后,從新回到控制器層面,此時(shí)應(yīng)該是大松了一口氣了,到目前為止,我們一大堆系列的工作都已經(jīng)做完了,只是還有一點(diǎn)失望的感覺(jué)是暫時(shí)還沒(méi)看到是否真的有卵用,這就好比十年鑄一劍,繼而十年在磨一劍,看不到成效始終覺(jué)得心中似有虧欠轰绵。

到目前為止,我們?cè)诳刂破鲗用婺茏龅膬H有的是范型數(shù)據(jù)的操作,已經(jīng)安全沒(méi)有邏輯了,邏輯全部壓入了Model,下面就看下控制器層面的邏輯:

//處理action

  • (TapBlock)tapBlockForModel:(id)model
    {
    __weak typeof (self) weakself = self;
    return ^(NSIndexPath * indexPath){
    if ([model conformsToProtocol:@protocol(TemplateActionProtocol)]) {
    TemplateAction *action = [(id)model jumpFloorModelAtIndexPath:indexPath];
    [weakself.handler handlerAction:action];
    }
    };
    }

//注冊(cè)cell

[self.tableView registerClass:[TemplateFocusCell class] forCellReuseIdentifier:@"TemplateFocusCell"];

//tableView 代理實(shí)現(xiàn)
//pragma mark - UITableViewDataSource,UITableViewDelegate

  • (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
    return [self.floorModel.floors count];
    }

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    TemplateContainerModel *list = self.floorModel.floors[section];
    return [list numberOfChildModelsInContainer];
    }

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    id model = [self.floorModel rowModelAtIndexPath:indexPath];
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:[model floorIdentifier]];
    [cell processData:model];
    [cell tapOnePlace:[self tapBlockForModel:model]];
    if(!cell){
    return [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    }else{
    return (UITableViewCell *)cell;
    }
    }

  • (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{

    id floor = [self.floorModel rowModelAtIndexPath:indexPath];
    if ([floor respondsToSelector:@selector(floorIdentifier)]) {
    NSString *cellIdentifier = [floor floorIdentifier];
    Class viewClass = NSClassFromString(cellIdentifier);
    CGSize size = [viewClass calculateSizeWithData:floor constrainedToSize:CGSizeMake(tableView.frame.size.width, 0.0)];
    return size.height;
    }
    return 0;
    }

  • (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    {
    id floor = self.floorModel.floors[section];
    if ([floor conformsToProtocol:@protocol(TemplateSorbRenderProtocol)]) {
    NSString *headerIdentifier = [floor headerFloorIdentifier];
    if (headerIdentifier) {
    Class viewClass = NSClassFromString(headerIdentifier);
    CGSize size = [viewClass calculateSizeWithData:floor constrainedToSize:CGSizeMake(tableView.frame.size.width, 0.0)];
    return size.height;
    }
    }

    return 0;
    }

  • (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
    {
    id floor = self.floorModel.floors[section];
    if ([floor conformsToProtocol:@protocol(TemplateSorbRenderProtocol)]) {
    id headerModel = [floor headerFloorModelAtIndex:section];
    if (headerModel) {
    NSString *identifier = [headerModel headerFloorIdentifier];
    UIView *headerView = (UIView *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:identifier];
    [headerView processData:floor];
    return headerView;
    }
    }
    return nil;
    }
    至此,控制器只剩下以上操作,相對(duì)來(lái)說(shuō)已經(jīng)最大限度的梳理了邏輯,將所有的邏輯壓入Model,如果服務(wù)端新增了新型的業(yè)務(wù)場(chǎng)景的數(shù)據(jù),依舊可以通過(guò)協(xié)議層的適配,將數(shù)據(jù)最終的組裝上述模式,最后直接拿來(lái)使用,如果需要修改對(duì)應(yīng)的View,直接可以在Model內(nèi)修改具體的將要渲染的View的名字即可,這些工作都跟控制器層沒(méi)有任何關(guān)系粉寞。

在Action協(xié)議中,具有Action操作的Model會(huì)在使用過(guò)程中實(shí)現(xiàn)TemplateActionProtocol這一協(xié)議,在事件處理的時(shí)間會(huì)拋出這樣一個(gè)ActionModel,之后此處我們會(huì)直接對(duì)Action對(duì)象handler操作,此處并沒(méi)有控制器層UI界面的操作,這一點(diǎn)遵循了設(shè)計(jì)模式中的命令行模式(這一點(diǎn)原理脫胎于于strus框架中XWork框架,將控制器與UI工作無(wú)關(guān)的內(nèi)務(wù)以命令行的模式跑出來(lái),放在別的一個(gè)代理中去完成,這樣能夠最大的限度的做到對(duì)控制器層面的瘦身工作)。

說(shuō)到控制器瘦身工作,iOS常用的大概是就是Category了,將部分全局型屬性左腔、邏輯放在對(duì)應(yīng)的分類里面,有助于邏輯的抽離唧垦、代碼的分割。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末液样,一起剝皮案震驚了整個(gè)濱河市振亮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鞭莽,老刑警劉巖坊秸,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異撮抓,居然都是意外死亡妇斤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門丹拯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)站超,“玉大人,你說(shuō)我怎么就攤上這事乖酬∷老啵” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵咬像,是天一觀的道長(zhǎng)算撮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)县昂,這世上最難降的妖魔是什么肮柜? 我笑而不...
    開(kāi)封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮倒彰,結(jié)果婚禮上审洞,老公的妹妹穿的比我還像新娘。我一直安慰自己待讳,他們只是感情好芒澜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著创淡,像睡著了一般痴晦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琳彩,一...
    開(kāi)封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天誊酌,我揣著相機(jī)與錄音部凑,去河邊找鬼。 笑死术辐,一個(gè)胖子當(dāng)著我的面吹牛砚尽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辉词,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼必孤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瑞躺?” 一聲冷哼從身側(cè)響起敷搪,我...
    開(kāi)封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎幢哨,沒(méi)想到半個(gè)月后赡勘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捞镰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年闸与,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岸售。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡践樱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凸丸,到底是詐尸還是另有隱情拷邢,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布屎慢,位于F島的核電站瞭稼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏腻惠。R本人自食惡果不足惜环肘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望集灌。 院中可真熱鬧悔雹,春花似錦、人聲如沸绝页。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)续誉。三九已至,卻和暖如春初肉,著一層夾襖步出監(jiān)牢的瞬間酷鸦,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留臼隔,地道東北人嘹裂。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像摔握,于是被迫代替她去往敵國(guó)和親寄狼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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