轉自:http://www.jianshu.com/p/f7ff18ac1c31html
- 傳統模式下的開發
- MVC
- MVVM
- 基於面向協議MVP的介紹
- MVP實戰開發
說在前面:
相信就算你是個iOS新手也應該據說過MVC的,MVC是構建iOS App的標準模板。隨着時間的推移,在iOS平臺上MVC也逐漸開始面臨着愈來愈多的問題,最近又開始流行MVVM,MVVM使由MVC衍生而來,MVVM做爲一種新的開發模式和響應式編程相結合用來解決一部分業務場景等,今天,本我我要介紹給你們的是一個新的方式來架構你的App: Model-View-Protocol,暫時能夠理解爲是基於協議的一種設計規範,拿出你的流行語bingo card,由於咱們即將進行一次範式轉變。前端
第一次聽到MVC這個名詞是在C#中,相信對於MVC你們都已經很熟悉了,做爲一種軟件設計模式,MVC這個概念已經誕生好多年了。react
若是你已經開發一段時間的iOS應用,你必定據說過Model-View-Controller,在iOS開發中Apple從一開始就給咱們引入這一理念,相信這個名詞你們都不陌生。android
模型-視圖-控制器(Model-View-Controller,MVC)是Xerox PARC在20世紀80年代爲編程語言Smalltalk-80發明的一種軟件設計模式,至今已普遍應用於用戶交互應用程序中。在iOS開發中MVC的機制被使用的淋漓盡致,充分理解iOS的MVC模式,有助於咱們程序的組織合理性。ios
模型對象封裝了應用程序的數據,並定義操控和處理該數據的邏輯和運算。例如,模型對象多是表示商品數據list。用戶在視圖層中所進行的建立或修改數據的操做,經過控制器對象傳達出去,最終會建立或更新模型對象。模型對象更改時(例如經過網絡鏈接接收到新數據),它通知控制器對象,控制器對象更新相應的視圖對象。git
視圖對象是應用程序中用戶能夠看見的對象。視圖對象知道如何將本身繪製出來,並可能對用戶的操做做出響應。視圖對象的主要目的,就是顯示來自應用程序模型對象的數據,並使該數據可被編輯。儘管如此,在 MVC 應用程序中,視圖對象一般與模型對象分離。程序員
在iOS應用程序開發中,全部的控件、窗口等都繼承自 UIView,對應MVC中的V。UIView及其子類主要負責UI的實現,而UIView所產生的事件均可以採用委託的方式,交給UIViewController實現。github
在應用程序的一個或多個視圖對象和一個或多個模型對象之間,控制器對象充當媒介。控制器對象所以是同步管道程序,經過它,視圖對象瞭解模型對象的更改,反之亦然,控制器主要負責數據的傳遞解耦等工做。控制器對象還能夠爲應用程序執行設置和協調任務,並管理其餘對象的生命週期。web
控制器對象解釋在視圖對象中進行的用戶操做,並將新的或更改過的數據傳達給模型對象。模型對象更改時,一個控制器對象會將新的模型數據傳達給視圖對象,以便視圖對象能夠顯示它。編程
M和V永遠不能相互通訊,只能經過控制器傳遞。控制器能夠直接與Model對話(讀寫調用Model),Model經過通知或者KVO機制與控制器間接通訊。控制器能夠直接與View對話,經過outlet,直接操做View,outlet直接對應到View中的控件,View經過action向控制器報告事件的發生(如用戶的點擊事件)。控制器是View的直接數據源(數據極可能是控制器從Model中取得並通過加工了)。控制器是View的代理(delegate),以同步View與Controller。
MVC是一個用來組織代碼的權威範式,也是構建iOS App的標準模式。Apple甚至是這麼說的。在MVC下,全部的對象被歸類爲一個model,一個view,或一個controller。Model持有數據,View顯示與用戶交互的界面,而View Controller調解Model和View之間的交互。然而,隨着模塊的迭代咱們愈來愈發現MVC自身存在着不少不足。所以,MVVM從其餘應用而出,在 iOS中今後咱們徹底將業務邏輯加以區分並使用這套思想。
在上圖中,view將用戶交互通知給控制器。view的控制器經過更新Model來反應狀態的改變。Model(一般使用Key-Value-Observation)通知控制器來更新他們負責的view。大多數iOS應用程序的代碼使用這種方式來組織。
在傳統的app中模型數據通常都很簡單,不涉及到複雜的業務數據邏輯處理,客戶端開發受限於它自身運行的的平臺終端,這一點註定使移動端不像PC前端那樣可以處理大量的複雜的業務場景。然而隨着移動平臺的各類深刻,咱們不的不考慮這個問題。傳統的Model數據大多來源於網絡數據,拿到網絡數據後客戶端要作的事情就是將數據直接按照順序畫在界面上。隨着業務的愈來愈來的深刻,咱們依賴的service服務可能在大多時間沒法第一時間知足客戶端須要的數據需求,移動端愈發的要自行處理一部分邏輯計算操做。這個時間一慣的作法是在控制器中處理,最終致使了控制器成了垃圾箱,愈來愈不可維護。
控制器Controller是app的「膠水代碼」:協調模型和視圖之間的全部交互。控制器負責管理他們所擁有的視圖的視圖層次結構,還要響應視圖的loading、appearing、disappearing等等,同時每每也會充滿咱們不肯暴露的Model的模型邏輯以及不肯暴露給視圖的業務邏輯。這引出了第一個關於MVC的問題...
視圖view一般是UIKit控件(component,這裏根據習慣譯爲控件)或者編碼定義的UIKit控件的集合。進入.xib或者Storyboard會發現一個app、Button、Label都是由這些可視化的和可交互的控件組成。你懂的。View不該該直接引用Model,而且僅僅經過IBAction事件引用controller。業務邏輯很明顯不納入view,視圖自己沒有任何業務。
厚重的View Controller因爲大量的代碼被放進viewcontroller,致使他們變的至關臃腫。在iOS中有的view controller裏綿延成千上萬行代碼的事並非前所未見的。這些超重app的突出狀況包括:厚重的View Controller很難維護(因爲其龐大的規模);包含幾十個屬性,使他們的狀態難以管理;遵循許多協議(protocol),致使協議的響應代碼和controller的邏輯代碼混淆在一塊兒。
厚重的view controller很難測試,無論是手動測試或是使用單元測試,由於有太多可能的狀態。將代碼分解成更小的多個模塊一般是件好事。
太過於輕量級的Model,早期的Model層,其實就是若是數據有幾個屬性,就定義幾個屬性,ARC普及之後咱們在Model層的實現文件中基本上看不到代碼( 無需再手動管理釋放變量,Model既沒有複雜的業務處理,也沒有對象的構造,基本上.m文件中的代碼廣泛是空的);同時與控制器的代碼越來厚重造成強烈的反差,這一度讓人不由對現有的開發設計構思有所懷疑。
蘋果使用的MVC的定義是這麼說的:全部的對象均可以被歸類爲一個Model,一個view,或是一個控制器。就這些。那麼把網絡代碼放哪裏?和一個API通訊的代碼應該放在哪兒?
你可能試着把它放在Model對象裏,可是也會很棘手,由於網絡調用應該使用異步,這樣若是一個網絡請求比持有它的Model生命週期更長,事情將變的複雜。顯然也不該該把網絡代碼放在view裏,所以只剩下控制器了。這一樣是個壞主意,由於這加重了厚重控制器的問題。
那麼應該放在那裏呢?顯然MVC的3大組件根本沒有適合放這些代碼的地方。
MVC的另外一個大問題是,它不鼓勵開發人員編寫單元測試。因爲控制器混合了視圖處理邏輯和業務邏輯,分離這些成分的單元測試成了一個艱鉅的任務。大多數人選擇忽略這個任務,那就是不作任何測試。
上文提到了控制器能夠管理視圖的層次結構;控制器有一個「view」屬性,而且能夠經過IBOutlet訪問視圖的任何子視圖。當有不少outlet時這樣作不易於擴展,在某種意義上,最好不要使用子視圖控制器(child view controller)來幫助管理子視圖。
在這裏有多個模糊的標準,彷佛沒有人能徹底達成一致。貌似不管如何,view和對應的controller都牢牢的耦合在一塊兒,總之,仍是會把它們當成一個組件來對待。Apple提供的這個組件一度以來在某種程度誤導了大多初學者,初學者將全部的視圖所有拖到xib中,鏈接大量的IBoutLet輸出口屬性,都是一些列問題。
在經歷了一大堆吐槽以後,誕生了MVVM(一個高大尚牛逼哄哄的名詞,今後又多了一種人,你懂MVVM ?若是你的回答是否,瞬間被鄙視一把)。
新思惟
其實MVVM聽說最先在微軟的.NET平臺中出現過(具體什麼背景,什麼緣由就不一一介紹了,仍是要感謝偉大的.NET平臺工程師,造劍不如造經,世間萬道皆不離其宗),不管是是MVVM仍是MVC咱們無需堅持反對或者迷戀於它。在MVVM中他的設計思路和MVC很像。它正式規範了視圖和控制器緊耦合的性質,並引入新的組件。
Model-View-ViewModel
在理想的世界裏,MVC也許工做的很好。然而,咱們生活在真實的世界。既然咱們已經詳細說明了MVC在典型場景中的問題,那讓咱們看一看一個可供替換的選擇:Model-View-ViewModel。
在MVVM裏,view和view controller正式聯繫在一塊兒,咱們把它們視爲一個組件。視圖view仍然不能直接引用模型Model,固然controller也不能。相反,他們引用視圖模型view Model。
view Model是一個放置用戶輸入驗證邏輯,視圖顯示邏輯,發起網絡請求和其餘各類各樣的代碼的極好的地方。有一件事情不該納入view Model,那就是任何視圖自己的引用。view Model的概念同時適用於於iOS和OS X。(換句話說,不要在view Model中使用 #import UIKit.h)
因爲展現邏輯(presentation logic)放在了view Model中(好比Model的值映射到一個格式化的字符串),視圖控制器自己就會再也不臃腫。當你開始使用MVVM的最好方式是,能夠先將一小部分邏輯放入視圖模型,而後當你逐漸習慣於使用這個範式的時候再遷移更多的邏輯到視圖模型中。
以個人經驗,使用MVVM會輕微的增長代碼量,但整體上減小了代碼的複雜性。這是一個划算的交易。
回過頭再來看MVVM的圖示,你會注意到我使用了模糊的動詞「notify」和「update」,而沒有詳細說明該怎麼作。你可使用KVO,就像MVC那樣,但這很快就會變得難以管理。事實上,使用ReactiveCocoa會是更好的方式來組織各個部分。
關於怎麼結合ReactiveCocoa來使用MVVM的信息,能夠閱讀開源app。你也能夠閱讀個人關於ReactiveCocoa和MVVM的書。
關於MVVM的具體細節此處就很少詳細介紹,此處重在作對比分析,若是瞭解的能夠去了解相關資料。
曾經有無數我的總喜歡問我大家的iOS採用什麼樣的架構,其實每次被問到這樣的問題,不是瞬間被萌了,就是想本身問本身iOS也有架構??
上文提到了MVC、MVVM,真實的業務場景中,若是場景的邏輯異常複雜,在反覆的迭代中仍會出現各式各樣的問題。真對MVVM我我的理解主要是將原來Controller中處理數據邏輯的代碼統一歸到一個新的class(viewModel)中去,更甚之網絡請求等工做所有從Controller移到viewModel。剛一開始總覺的怪怪的。現階段客戶端開發愈來愈進入一個2.0的階段,早期的app功能都相對比較簡單,不管是從界面仍是從業務邏輯上給人的感受都是簡潔實用,這中間包括UI的設計、功能的設計、產品的設計定位等。隨着行業的深刻,用戶的過渡依賴移動端最終致使業各式各樣的業務更加依賴客戶端,這就致使客戶端的開發不得不向PC端靠齊,在版本的反覆迭代中業務場景變的愈發不盡人意,彷彿又回到了軟件設計的早期。
在傳統軟件領域,從MVC的誕生主要是爲了解決軟件界面的行爲的分離,在複雜的業務場景內會進一步區分業務邏輯場景的分離,這些手段的最終的目的都是盡最大限度的下降整個場景的藕合度,使其達到分離的目的,模塊與模塊最終獲得獨立,將整個場景化整爲零,最終使每一個模塊在一個零上工做,這對於不管是軟件的開發仍是後續的維護、以及使用廣泛遵循這個原則,現有的模式大概產生了相關的相似架構。
傳統web架構裏面是這樣解決的 :
現有客戶度一度採用下面的模式:
客戶端經過service拿到json 數據,而後經過MVC的結構展現到UI界面上,在iOS中一直流行MVC的開發模式,經過與傳統開發模式對比能夠發現,其實
service層-客戶端交互與服務端service服務知足外部業務場景無非是兩個互逆的過程(一個輸出層,一個輸入層,都是爲了更好的知足的下一步的業務需求,一個是將原始數據邏輯話,一個是將得到邏輯數據存檔而且展現到用戶面前)。service層根據具體的業務場景提供對應的數據服務,service根據不一樣的業務場景經過DTO層拿到對應
的數據而後組織好數據提供給外界(service 層負責將原始物理數據轉換成對應的邏輯數據提供給外界)。
相反,客戶端經過網絡層拿到對應的網絡數據繪製到對應的View上,可是實際的開發過程當中,網絡數據與真實客戶端使用場景也是有必定的差距,MVVM層將對應的
一部分邏輯處理移植到了ViewModel中,這並無從根本上解決問題,無非是將代碼作了一份對應的copy轉移,並無從根本上達到邏輯分層的概念。相反MVP模
式剛好解決了這一難題,MVP模式衍生於傳統service架構,針對不一樣的業務場景圖供對應的匹配的抽象service服務,客戶端拿到網絡數據後未達到指定的目的,
爲知足相同抽象邏輯的業務場景,在客戶端網絡層與Model層之間加一協議層,Model層實現整個協議層,以後在基於MVC的結構下將一律相同層次的
業務場景繪製解釋到對應的View上。
Model層相似於MVVM的ViewModel,主要負責存儲抽象邏輯數據,另外Model層主還有部分工做實現對應的協議層協議,提供協議對應的各類屬性以及服務。Model通過協議層抽象約束,最後Model被抽象成具備統一抽象邏輯的業務場景,最終Model層在講數據交付整個MVC結構繪製展現的時間,咱們能夠按照同一套抽象的邏輯標準去執行。
在傳統的web層面,爲了知足各式各樣的業務邏輯場景服務,最紅咱們實現軟件羅傑的層次的分離,誕生了service服務這個概念(service就相似一個標準尺寸的水龍頭出口,只要對應的水龍頭都按照這樣的規則來生產,service就可以知足格式各樣的業務場景,極大的解決的傳統軟件服務業務場景層次的一系列難題);相同的原理在客戶端一樣可使用,爲了知足客戶端MVC結構層裏面的穩定,避免各式各樣的業務場景迭代插入不一樣的邏輯,避免最終軟件危機的產生,咱們採用追加協議層的模式來知足這一目的。
遍觀整個軟件開發,從早期的軟件開發,到後來軟件生產管理的危機,軟件開發模式一步步的確立,軟件行業的每一個階段都是一個里程碑。這世間沒有相對完美獨到的設計法則,可是亙古不變永遠只有一個那就是軟件的開發更佳面相生產化、規範化、更加的利於可維護化。一直以來我本人並不特別的注重軟件的設計必定、必須按照某種規則來作,畢竟不一樣的人、不一樣的業務場景、不一樣的工程師總有不一樣的實際境況,站在一個開發工程師的角度來講我並不執拗於都按照固定的規則來(好比說你必須按照某個模式來作,必須用MVVM來作;必須用ReactCocoa信號型機制來作...)。相反我我的認爲太過於執拗只不過某些人的一廂情願的罷了。相反我以爲因地制宜、應運而生豈不更加快哉,設計不拘於模式,更多時間更是不侷限於思考。不管是MVVM、MVP哪個不是脫胎於MVC,這個世間萬變不離其宗,萬千功法始終都離不開一部最終的母經。
說了這麼多,下面上實戰例子。
大概描述一下業務場景,做爲電商app,咱們但願在原生的基礎上開發一套定製的可控、可配、可維護的通用型原生模版(至於說的這麼靈活 有多麼的好,爲啥不用H五、ReactNative,這個問題不要來問我,產品狗們讓作原生,程序員只能執行)。大概是這樣一個場景,能夠配置的樓層樣式多達十幾種(至少目前已經有十幾種,之後可能會更多);每種可配置樓層樣式是多元的,外觀長相不一,數據格式也不盡相同但有部分類同;要求後臺CMS配置界面配置法則有共同類似之處;要求每種樣式樓層處理事件記憶跳轉不盡相同;最可恨的頁面已經很長了之後會源源不斷加入新的模版。
考慮到長遠,這樣的複雜樓層,若是仍舊按照傳統的模式來作,問題會不少,數據沒法統1、沒法統一繪製、沒法統一處理。具體場景相信你們應該理解了。
上設計思路
潛在問題
邏輯建模分析
暫時能夠將每種模版樓層的總體數據做爲一個容器Modle,主要負責該樓層的總體數據調度
將每種樓層公有的屬性以及內容抽象出來放入一個容器父類Container,而後將不一樣模版特有的屬性放在子模版派生Model中,做爲派生屬性
對準一個容器類,我能夠將每種容器Model的使用法則抽象總結概括(一、樓層是否有Header,是否要吸頂;二、該樓層具體要由什麼樣的View模版去繪製; 三、樓層內容是繪製在單個section單個cell中仍是繪製在多行上; 四、每一個樓層的元素點擊跳轉處理等). 咱們將容器這塊做爲一個數據源概念最終抽象出一套可供外界獲取數據的Interface(Protocol)
當咱們拿到樓層數據後在父容器基礎上作子樓層的派生,咱們要求派生容器去實現上述父容器的Protocol,經過協議咱們能夠知道具體的繪製的目標,以及要繪製的元素個數等,最終達到一個目的,
將每一個樓層的數據裝配在咱們定義好的一個適配器容器內,而後經過協議給外界提供一套統一的操做入口,以後咱們才用統一的操做方式操做容器,最終實現一個容器對應一個樓層。
Render 協議,在這個咱們對準每一個要具體繪製到UI上的Model,咱們統一讓其實現Render協議,經過適配器容器咱們咱們拿到具體要繪製的目標,
目標繪製題都實現了Render協議,在Render協議咱們能夠拿到具體當前Model將由哪一個具體的Cell去呈遞。在每一個繪製目標題內由Model決定
當前內容由什麼樣式的cell模版去繪製。咱們把全部的樓層數據處理邏輯壓在適配器容器內,再將Model的繪製目標都交由Model本身決定。
實現上述目標後,在ViewController層面,咱們看到的只有一個實現了適配器協議的Model數組,在 table的繪製過程咱們經過操做一個
id<適配器Protocol> 類型的Model對象, 拿到這個具體的索引對應的對象後,經過內部已經實現的協議咱們很快的拿到下一個要繪製的目標Model
而後再拿到具體的Cell模版的Identifier,而後從tableview中取到當前Identifier對應的cell模版,傳入數據最後返回一個cell。
這個地方咱們先定義了一個適配容器協議,以及一個父容器類,咱們將樓層公有屬性放在父類中,將派生容器子類(具體的樓層Model)實現一個抽象協議。
MVP模式的原則是,在service層提供一大堆不盡相同的業務場景以後,咱們將這一系列數據所有抽象概括,經過定製一套標準的protocol協議,讓不一樣業務場景的都去實現這一協議,最終將數據所有裝配、拼裝成一套具備相同調度規則的統一編制話數據Model,以後咱們採用id<protocol>的標準去操做數據。
MVP除了將數據邏輯徹底鎮封的各自的Model,同時將那些Model須要繪製,哪些Model須要校驗, 哪些Model須要接受處理點擊Action這些邏輯所有由Model本身來決定,Controller只做爲一個粘合性的模版,Controller只處理一批具備共性的範型類數據,至於具體的操做操做絕不關心。
MVP面相的更多的是在MVC上層與service之間追加了一層協議層,咱們認爲經過協議層處理過的數據是暫時能夠客戶端場景使用的數據,在數據到達MVC咱們針對數據進行再加工、再構造處理,這一切所有在容器內操做。這一點徹底與別的設計模式相反。
MVP並非讓用戶在Model打上網絡請求操做、在Model層執行[self.navigationController pushViewController:*]等這些操做,其實相對大多人來講對於部分對象生命週期長短問題仍是很在意,因此在處理TemplateActionProtocol協議的時間,MVP只是對準Action作了一層抽象封裝。經過實現TemplateActionProtocol的Model會產生一個Action對象,最終經過block的調用鏈傳回控制器中,咱們在控制器中統一作handler處理。
MVP咱們最初設計目的就是爲了強調一個裝配概念,若是發生了業務場景的追加,控制器我不會改動其中的代碼,只須要將新數據追加成相同批次的ViewModel,而後配置進容器,以後控制器不作任何修改就能夠知足需求了。經過具體的剝離、抽取咱們成功了的最大限度的剝離了控制器,知足了輕量級Controller這一律念。
MVP與傳統軟件相比,在設計這一點的時間咱們徹底借鑑了傳統軟件的思惟模式,Java平臺的service設計模式、三層架構這些設計規範都相對作了一些對比分析,最終得出了MVP這一理念。
分析場景完畢,下面來分析一個模版的例子來講命一切吧!!
Template 協議
ViewController
//TemplateRenderProtocol.h @protocol TemplateRenderProtocol <NSObject,TemplateActionProtocol> @required - (NSString *)floorIdentifier; @end //TemplateSorbRenderProtocol.h @protocol TemplateSorbRenderProtocol <NSObject> - (NSString *)headerFloorIdentifier; - (id <TemplateSorbRenderProtocol>)headerFloorModelAtIndex:(NSInteger)index; @end //TemplateActionProtocol.h */ @protocol TemplateActionProtocol <NSObject> @optional - (TemplateAction *)jumpFloorModelAtIndexPath:(NSIndexPath *)indexPath; @end //TemplateCellProtocol.h @protocol TemplateBaseProtocol; typedef void (^TapBlock) (NSIndexPath* index); @protocol TemplateCellProtocol <NSObject> @optional + (CGSize)calculateSizeWithData:(id<TemplateRenderProtocol>)data constrainedToSize:(CGSize)size; - (void)processData:(id <TemplateRenderProtocol>)data; - (void)tapOnePlace:(TapBlock) block; @end
// TemplateContainerModel.h /** * 容器概念 */ @protocol TemplateContainerProtocol <NSObject> @required - (NSInteger)numberOfChildModelsInContainer; - (id <TemplateRenderProtocol>)childFloorModelAtIndex:(NSInteger)index; @end @class TemplateChannelModel; @interface TemplateContainerModel : NSObject<TemplateContainerProtocol,TemplateActionProtocol,TemplateRenderProtocol> //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
下面的就先引用一個具體的業務場景吧,頂部banner樓層,每一個大的樓層都是一個容器Model,是繼承於父容器,而且會適當重寫父類協議以及方法
//TemplateFloorFocusModel.h //此處,banner是多個對象繪製成輪播的樣式,總體是繪製在同一個cell上的,因此TemplateFloorFocusModel首先是一個容器類,是具備數據源的 功能,可是他又是一個繪製目標Model,TemplateFloorFocusModel實現了Render協議,就決定這個接下來會將TemplateFloorFocusModel繪製到UI界面上(若是此處的容器存儲的是一個section下的list形式,容器類就無需實現render協議,只須要將list 中的Model實現render協議便可) @interface TemplateFloorFocusModel : TemplateContainerModel<TemplateRenderProtocol> @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; } //(若是此處的容器存儲的是一個section下的list形式,此處返回一個實現render協議的Model便可) - (id <TemplateRenderProtocol>)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設計此處咱們才用方式依舊是將Cell做爲模版,將對應的視圖邏輯統一放在一個UIViewSubView中, 以後在Cell中將View直接add到cell.ContentView上。
針對焦點圖cell TemplateFocusCell咱們有一個TemplateFocusView來對應,下面看下代碼設計
TemplateFocusCell
// TemplateFocusCell @interface TemplateFocusCell : UITableViewCell<TemplateCellProtocol> @end @interface TemplateFocusCell (){ TemplateFocusView *_focusView; } @property (nonatomic,strong) id <TemplateRenderProtocol> 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 <TemplateRenderProtocol>)data { if([data isKindOfClass:[TemplateFloorFocusModel class]]) { self.data = data; [_focusView processData:(id <TemplateRenderProtocol>)data]; } } + (CGSize)calculateSizeWithData:(id<NSObject>)data constrainedToSize:(CGSize)size { // id<TemplateRenderProtocol> model = data; CGSize curSize = CGSizeMake(ScreenWidth, 110); return curSize; } - (void)tapOnePlace:(TapBlock) block { [_focusView setTapBlock:block]; }
TemplateFocusView
@interface TemplateFocusView : UIView<TemplateCellProtocol> @end @interface TemplateFocusView ()<iCarouselDataSource,iCarouselDelegate> { 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<TemplateRenderProtocol>)data constrainedToSize:(CGSize)size { return size; } - (void)processData:(id <TemplateRenderProtocol>)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層能夠看到,咱們仍舊遵循以往的模式,將cell高度的計算,最終放在View中來完成(此處咱們並無Model化,而是仍舊遵循你們的習慣,具體的高度根據具體的視圖場景來控制),看到此處的計算高度的方法,接下來的問題就很少說了....
在作完以上的一些列的邏輯化抽象工做之後,重新回到控制器層面,此時應該是大鬆了一口氣了,到目前爲止,咱們一大堆系列的工做都已經作完了,只是還有一點失望的感受是暫時還沒看到是否真的有卵用,這就比如十年鑄一劍,繼而十年在磨一劍,看不到成效始終以爲心中似有虧欠。
到目前爲止,咱們在控制器層面能作的僅有的是範型數據的操做,已經安全沒有邏輯了,邏輯所有壓入了Model,下面就看下控制器層面的邏輯:
//處理action - (TapBlock)tapBlockForModel:(id<TemplateRenderProtocol>)model { __weak typeof (self) weakself = self; return ^(NSIndexPath * indexPath){ if ([model conformsToProtocol:@protocol(TemplateActionProtocol)]) { TemplateAction *action = [(id<TemplateActionProtocol>)model jumpFloorModelAtIndexPath:indexPath]; [weakself.handler handlerAction:action]; } }; } //註冊cell [self.tableView registerClass:[TemplateFocusCell class] forCellReuseIdentifier:@"TemplateFocusCell"]; //tableView 代理實現 //pragma mark - UITableViewDataSource,UITableViewDelegate - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.floorModel.floors count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { TemplateContainerModel<TemplateContainerProtocol> *list = self.floorModel.floors[section]; return [list numberOfChildModelsInContainer]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { id <TemplateRenderProtocol> model = [self.floorModel rowModelAtIndexPath:indexPath]; UITableViewCell <TemplateCellProtocol> * 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 <TemplateRenderProtocol> floor = [self.floorModel rowModelAtIndexPath:indexPath]; if ([floor respondsToSelector:@selector(floorIdentifier)]) { NSString *cellIdentifier = [floor floorIdentifier]; Class<TemplateCellProtocol> 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 <TemplateSorbRenderProtocol,TemplateRenderProtocol> floor = self.floorModel.floors[section]; if ([floor conformsToProtocol:@protocol(TemplateSorbRenderProtocol)]) { NSString *headerIdentifier = [floor headerFloorIdentifier]; if (headerIdentifier) { Class<TemplateCellProtocol> 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 <TemplateSorbRenderProtocol,TemplateRenderProtocol> floor = self.floorModel.floors[section]; if ([floor conformsToProtocol:@protocol(TemplateSorbRenderProtocol)]) { id<TemplateSorbRenderProtocol> headerModel = [floor headerFloorModelAtIndex:section]; if (headerModel) { NSString *identifier = [headerModel headerFloorIdentifier]; UIView <TemplateCellProtocol> *headerView = (UIView <TemplateCellProtocol> *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:identifier]; [headerView processData:floor]; return headerView; } } return nil; }
至此,控制器只剩下以上操做,相對來講已經最大限度的梳理了邏輯,將全部的邏輯壓入Model,若是服務端新增了新型的業務場景的數據,依舊能夠經過協議層的適配,將數據最終的組裝上述模式,最後直接拿來使用,若是須要修改對應的View,直接能夠在Model內修改具體的將要渲染的View的名字便可,這些工做都跟控制器層沒有任何關係。
在Action協議中,具備Action操做的Model會在使用過程當中實現TemplateActionProtocol這一協議,在事件處理的時間會拋出這樣一個ActionModel,以後此處咱們會直接對Action對象handler操做,此處並無控制器層UI界面的操做,這一點遵循了設計模式中的命令行模式(這一點原理脫胎于于strus框架中XWork框架,將控制器與UI工做無關的內務以命令行的模式跑出來,放在別的一個代理中去完成,這樣可以最大的限度的作到對控制器層面的瘦身工做)。
說到控制器瘦身工做,iOS經常使用的大概是就是Category了,將部分全局型屬性、邏輯放在對應的分類裏面,有助於邏輯的抽離、代碼的分割。
容器模式 適配器模式 命令行模式