本文demo地址:https://github.com/TheRuningAnt/TestMVPFramework.git
雖然做了好幾年iOS開發(fā)了,洋洋灑灑也寫過(guò)了很多代碼,也解決過(guò)一些棘手的需求,但是總是感覺(jué)自己寫的代碼不夠簡(jiǎn)潔,總是感覺(jué)邏輯不是很清晰,每天做的工作都只是在不斷的搬運(yùn)以前的東西拿來(lái)用,雖然功能都能夠?qū)崿F(xiàn),但是一旦單個(gè)頁(yè)面業(yè)務(wù)邏輯過(guò)多過(guò)復(fù)雜(比如一個(gè)完整的直播間內(nèi)容,直播、回放觀看及下載、彈幕、舉手發(fā)言、表情選擇、聊天列表、課件演示等等),總是容易寫出來(lái)長(zhǎng)長(zhǎng)長(zhǎng)長(zhǎng)的代碼。雖然已經(jīng)盡最大程度將各個(gè)業(yè)務(wù)進(jìn)行獨(dú)立的封裝,最大限度的實(shí)現(xiàn)代碼和方法的復(fù)用,但是等項(xiàng)目結(jié)束后仍然發(fā)現(xiàn)自己寫的項(xiàng)目雖然功能正常,卻實(shí)在過(guò)于冗雜,難以維護(hù),簡(jiǎn)而言之,就是感覺(jué)自己寫的代碼太垃圾!
而且不管大小項(xiàng)目,都會(huì)存在這個(gè)問(wèn)題,感覺(jué)自己的代碼架構(gòu)不夠精簡(jiǎn),雖然層級(jí)之間已經(jīng)最大程度解耦,但是單個(gè)層級(jí)內(nèi)的邏輯和代碼量還是會(huì)跟業(yè)務(wù)復(fù)雜度一起直線上升,雖然實(shí)現(xiàn)了視圖層和模型層的分離,但是往往或多或少仍然存在視圖層和數(shù)據(jù)模型層間直接通訊的情況(例如典型的cell.model = model),實(shí)在是看的很難受。一直想好好研究下代碼的設(shè)計(jì)架構(gòu),來(lái)讓自己的代碼可讀性和可維護(hù)性都可以有所長(zhǎng)進(jìn),但是一直時(shí)間不是很充裕(好吧,懶。。。),所以一直沒(méi)有靜下心來(lái)好好研究一番,直到又做完一個(gè)項(xiàng)目,看著兩千多行長(zhǎng)長(zhǎng)的代碼和復(fù)雜的業(yè)務(wù),陷入了沉思。。。
實(shí)在看不下去了,所以寫了這篇文章,簡(jiǎn)單的分析一下這幾年時(shí)間的累積對(duì)傳統(tǒng)MVC架構(gòu)的理解以及對(duì)MVP架構(gòu)的理解和使用示例。
不同的人對(duì)同一種架構(gòu)都會(huì)有著自己的理解,所以我只是簡(jiǎn)單的闡述下根據(jù)我自己的經(jīng)驗(yàn)以及使用的心得來(lái)淺淺的聊一下我對(duì)這兩種架構(gòu)的領(lǐng)悟,也歡迎圍觀的大佬和小朋友隨時(shí)指點(diǎn)。好了,進(jìn)入正題。
MVC架構(gòu)的優(yōu)勢(shì)和存在的問(wèn)題
MVC作為傳統(tǒng)的設(shè)計(jì)架構(gòu),已經(jīng)在很多端都有根深蒂固的使用歷史了,也是蘋果公司一直主推的設(shè)計(jì)架構(gòu),很多iOS開發(fā)人員用的最多也最熟悉的也是這種設(shè)計(jì)架構(gòu),感謝偉大的創(chuàng)始人,MVC架構(gòu)幫助我們建立良好的業(yè)務(wù)分層意識(shí)以及解決了很多很多的需求場(chǎng)景。MVC的核心理念是使用控制層來(lái)主導(dǎo)視圖層和模型層之間的數(shù)據(jù)溝通,視圖層和模型層不應(yīng)該有直接的交互,但是開發(fā)的過(guò)程中偶爾總是會(huì)不那么嚴(yán)謹(jǐn),例如在視圖層直接拿來(lái)模型層的數(shù)據(jù)來(lái)用啥的,投機(jī)取巧是萬(wàn)惡之源,呵。
MVC提出了數(shù)據(jù)和業(yè)務(wù)及視圖分層的概念,使得我們?cè)诖a編寫的過(guò)程中有意識(shí)的進(jìn)行基本的對(duì)網(wǎng)絡(luò)層、控制層、視圖層和模型層進(jìn)行分層解耦處理,但是隨著設(shè)備的發(fā)展及人們生活需求的提高,手機(jī)端的業(yè)務(wù)也越來(lái)越復(fù)雜,控制層一直被我們以萬(wàn)金油的形式來(lái)使用,不僅僅被我們拿來(lái)處理頁(yè)面視圖層級(jí)的控制顯示、還擔(dān)負(fù)著處理根據(jù)自身生命周期的變動(dòng)可能觸發(fā)的業(yè)務(wù)邏輯、數(shù)據(jù)的部分解析處理、各種系統(tǒng)組件和我們自定義的代理以及他們的響應(yīng)、定義的一堆屬性,以及他本身的主要工作:處理視圖層用戶產(chǎn)生的交互對(duì)模型數(shù)據(jù)進(jìn)行讀寫,并且同步反饋給視圖層進(jìn)行顯示刷新,可能Controller心里一萬(wàn)頭MMP但是并不想說(shuō)什么,只是越來(lái)越不想被我們那么輕易的梳理清楚自己寫的業(yè)務(wù)邏輯,時(shí)間長(zhǎng)了也越來(lái)越變得難以維護(hù),尤其是過(guò)了一個(gè)月之后,產(chǎn)品突然想:哎,那個(gè)啥,改一下。然后我們打開代碼,一看2000+的controller。。。
所以,MVC雖然經(jīng)典,但是遇到一些比較復(fù)雜的業(yè)務(wù)場(chǎng)景,很容易造成控制層的臃腫和厚重,變得難以梳理和維護(hù),還是要好好考慮一下如何給控制層減壓及想辦法抽取出來(lái)更多的內(nèi)容單獨(dú)處理,比如使用MVP的架構(gòu)模式。
MVP
MVP被很多人解析為Model-View-Presenter,其實(shí)在我看來(lái),MVP也是從MVC的基礎(chǔ)上衍生而來(lái),主要還是一個(gè)面向協(xié)議的編程模式。模型層跟MVC里的模型層一致,處于一個(gè)數(shù)據(jù)模型建立以及數(shù)據(jù)處理的層級(jí),但是視圖層就不會(huì)是單純的指一些基本視圖了,controller的視圖也應(yīng)該歸納在視圖層。而我們自定義的Presenter則擔(dān)任起了絕大部分可以抽離出來(lái)的業(yè)務(wù)邏輯,controller只是控制簡(jiǎn)單的視圖層級(jí)控制以及presenter的調(diào)度使用。presenter處理數(shù)據(jù)在模型層和視圖層的流通,模型層和視圖層不允許有任何直接交互。presenter和Controller通過(guò)代理的方式進(jìn)行互相調(diào)用,視圖層和presenter也可以通過(guò)代理或者直接交互的方式進(jìn)行數(shù)據(jù)流通,模型層對(duì)數(shù)據(jù)的更改和頁(yè)面的更新必須通過(guò)presenter來(lái)在頁(yè)面上進(jìn)行展示。所以我們需要再配套一個(gè)協(xié)議文件以及獨(dú)立的網(wǎng)絡(luò)層來(lái)保證一個(gè)完整嚴(yán)謹(jǐn)?shù)腗VP架構(gòu)模式。話不多說(shuō),下面是我自己寫的一個(gè)MVP實(shí)例文件
效果

文件夾結(jié)構(gòu)。

其中
service負(fù)責(zé)模擬一個(gè)異步的網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求
presenter則是作為主要的業(yè)務(wù)處理層級(jí)
controller負(fù)責(zé)presenter的創(chuàng)建和調(diào)度使用
view負(fù)責(zé)視圖及數(shù)據(jù)的更新展示
model負(fù)責(zé)數(shù)據(jù)的處理及作為數(shù)據(jù)的模型
TestPresenterProtocol 聲明頁(yè)面直接調(diào)用需要的代理方法,控制頁(yè)面直接交互的接口
該demo的基本功能是模擬數(shù)據(jù)加載,加載之后tableView里進(jìn)行展示,可以通過(guò)cell里的加減數(shù)值按鈕修改對(duì)應(yīng)的展示內(nèi)容,下拉刷新之后頁(yè)面數(shù)據(jù)重新加載回歸原始狀態(tài)。
經(jīng)過(guò)抽離之后,controller里的基本的代碼量如下:

就已經(jīng)實(shí)現(xiàn)了我們的業(yè)務(wù)需求。后來(lái)為了演示controller與presenter之間通過(guò)協(xié)議的互相調(diào)用,又添加了加載提示視圖的顯示和隱藏功能。這個(gè)代碼量跟我們之前實(shí)現(xiàn)同樣需求的controller相比,已經(jīng)大大減少了很多吧。其實(shí)一開始想將tableView也直接放在presenter里創(chuàng)建,controller使用的時(shí)候只需要直接從presenter里獲取就好,這樣代碼量將減少到30行左右。后來(lái)考慮了一下,盲目為了代碼精簡(jiǎn)而進(jìn)行過(guò)度抽取分離反而得不償失,tableView作為視圖還是應(yīng)該在controller里創(chuàng)建,presenter可以引用處理其邏輯,但是不應(yīng)該負(fù)責(zé)它的創(chuàng)建和加載以及生命周期,應(yīng)該專注于業(yè)務(wù)邏輯的處理,所以還是拿回controller里。
簡(jiǎn)單描述一下各個(gè)文件里的主要內(nèi)容:
Service:
模擬網(wǎng)絡(luò)請(qǐng)求,只是一個(gè)簡(jiǎn)單的數(shù)據(jù)加載,代碼寥寥幾行

TestPresenter里進(jìn)行了控制器和視圖組件的綁定以及網(wǎng)絡(luò)數(shù)據(jù)的加載

處理tableView的代理邏輯和cell中的點(diǎn)擊事件處理邏輯

以及給tableView添加下拉刷新等操作。
不過(guò)要注意presenter對(duì)controller及其中的視圖引用都為弱引用,避免循環(huán)引用的產(chǎn)生。
controller則負(fù)責(zé)presenter的創(chuàng)建、調(diào)用及實(shí)現(xiàn)代理協(xié)議中定義的方法

TestCell 視圖層負(fù)責(zé)接收用戶的交互事件并且通過(guò)代理反饋給presenter進(jìn)行處理以及數(shù)據(jù)的同步

還有我們的代理文件里的定義
TestPresenterProtocol 定義了cell的點(diǎn)擊代理以及controller展示/隱藏提示視圖的代理

這樣,我們就以MVP架構(gòu)實(shí)現(xiàn)了這樣一個(gè)簡(jiǎn)單的需求。如果presenter里的業(yè)務(wù)量比較繁雜,我們可以將整個(gè)業(yè)務(wù)分為幾個(gè)小模塊,通過(guò)創(chuàng)建presenter的category來(lái)實(shí)現(xiàn)業(yè)務(wù)的進(jìn)一步分層解耦,避免presenter文件過(guò)于臃腫的情況出現(xiàn)。
總結(jié): 其實(shí)在我看來(lái),不同的開發(fā)者對(duì)于同一種設(shè)計(jì)架構(gòu)都有自己的理解,實(shí)現(xiàn)的方式也都不盡相同。并不是說(shuō)對(duì)于某種架構(gòu)就一定有明確的文件夾路徑和定死的規(guī)范讓我們循規(guī)蹈矩的來(lái)使用,如果一板一眼的按照某種特定的方式來(lái)寫,那么了解更多的架構(gòu)模式反而會(huì)成為我們開發(fā)的累贅。重點(diǎn)是我們?cè)谠O(shè)計(jì)自己項(xiàng)目的時(shí)候有意識(shí)的去使用這種思想來(lái)減輕我們的代碼冗余量和控制層的厚重程度,讓我們的項(xiàng)目變得結(jié)構(gòu)清晰簡(jiǎn)潔、業(yè)務(wù)分層明了,便于他人閱讀和日后維護(hù)。而且合適的架構(gòu)并不一定意味著代碼量的減少,有時(shí)候反而代碼量和文件數(shù)會(huì)增加一些。但是采用新的架構(gòu)的原則一定是這個(gè)架構(gòu)可以讓我們的項(xiàng)目結(jié)構(gòu)變得清晰,維護(hù)成本大大降低。
完。