iOS MVC及MVP設(shè)計架構(gòu)淺析及使用實例

本文demo地址:https://github.com/TheRuningAnt/TestMVPFramework.git

雖然做了好幾年iOS開發(fā)了,洋洋灑灑也寫過了很多代碼,也解決過一些棘手的需求,但是總是感覺自己寫的代碼不夠簡潔,總是感覺邏輯不是很清晰,每天做的工作都只是在不斷的搬運以前的東西拿來用,雖然功能都能夠?qū)崿F(xiàn),但是一旦單個頁面業(yè)務(wù)邏輯過多過復雜(比如一個完整的直播間內(nèi)容,直播、回放觀看及下載、彈幕、舉手發(fā)言、表情選擇、聊天列表、課件演示等等),總是容易寫出來長長長長的代碼。雖然已經(jīng)盡最大程度將各個業(yè)務(wù)進行獨立的封裝,最大限度的實現(xiàn)代碼和方法的復用,但是等項目結(jié)束后仍然發(fā)現(xiàn)自己寫的項目雖然功能正常,卻實在過于冗雜,難以維護,簡而言之,就是感覺自己寫的代碼太垃圾!
而且不管大小項目,都會存在這個問題,感覺自己的代碼架構(gòu)不夠精簡,雖然層級之間已經(jīng)最大程度解耦,但是單個層級內(nèi)的邏輯和代碼量還是會跟業(yè)務(wù)復雜度一起直線上升,雖然實現(xiàn)了視圖層和模型層的分離,但是往往或多或少仍然存在視圖層和數(shù)據(jù)模型層間直接通訊的情況(例如典型的cell.model = model),實在是看的很難受。一直想好好研究下代碼的設(shè)計架構(gòu),來讓自己的代碼可讀性和可維護性都可以有所長進,但是一直時間不是很充裕(好吧,懶。。。),所以一直沒有靜下心來好好研究一番,直到又做完一個項目,看著兩千多行長長的代碼和復雜的業(yè)務(wù),陷入了沉思。。。
實在看不下去了,所以寫了這篇文章,簡單的分析一下這幾年時間的累積對傳統(tǒng)MVC架構(gòu)的理解以及對MVP架構(gòu)的理解和使用示例。

不同的人對同一種架構(gòu)都會有著自己的理解,所以我只是簡單的闡述下根據(jù)我自己的經(jīng)驗以及使用的心得來淺淺的聊一下我對這兩種架構(gòu)的領(lǐng)悟,也歡迎圍觀的大佬和小朋友隨時指點。好了,進入正題。
MVC架構(gòu)的優(yōu)勢和存在的問題
MVC作為傳統(tǒng)的設(shè)計架構(gòu),已經(jīng)在很多端都有根深蒂固的使用歷史了,也是蘋果公司一直主推的設(shè)計架構(gòu),很多iOS開發(fā)人員用的最多也最熟悉的也是這種設(shè)計架構(gòu),感謝偉大的創(chuàng)始人,MVC架構(gòu)幫助我們建立良好的業(yè)務(wù)分層意識以及解決了很多很多的需求場景。MVC的核心理念是使用控制層來主導視圖層和模型層之間的數(shù)據(jù)溝通,視圖層和模型層不應該有直接的交互,但是開發(fā)的過程中偶爾總是會不那么嚴謹,例如在視圖層直接拿來模型層的數(shù)據(jù)來用啥的,投機取巧是萬惡之源,呵。
MVC提出了數(shù)據(jù)和業(yè)務(wù)及視圖分層的概念,使得我們在代碼編寫的過程中有意識的進行基本的對網(wǎng)絡(luò)層、控制層、視圖層和模型層進行分層解耦處理,但是隨著設(shè)備的發(fā)展及人們生活需求的提高,手機端的業(yè)務(wù)也越來越復雜,控制層一直被我們以萬金油的形式來使用,不僅僅被我們拿來處理頁面視圖層級的控制顯示、還擔負著處理根據(jù)自身生命周期的變動可能觸發(fā)的業(yè)務(wù)邏輯、數(shù)據(jù)的部分解析處理、各種系統(tǒng)組件和我們自定義的代理以及他們的響應、定義的一堆屬性,以及他本身的主要工作:處理視圖層用戶產(chǎn)生的交互對模型數(shù)據(jù)進行讀寫,并且同步反饋給視圖層進行顯示刷新,可能Controller心里一萬頭MMP但是并不想說什么,只是越來越不想被我們那么輕易的梳理清楚自己寫的業(yè)務(wù)邏輯,時間長了也越來越變得難以維護,尤其是過了一個月之后,產(chǎn)品突然想:哎,那個啥,改一下。然后我們打開代碼,一看2000+的controller。。。

所以,MVC雖然經(jīng)典,但是遇到一些比較復雜的業(yè)務(wù)場景,很容易造成控制層的臃腫和厚重,變得難以梳理和維護,還是要好好考慮一下如何給控制層減壓及想辦法抽取出來更多的內(nèi)容單獨處理,比如使用MVP的架構(gòu)模式。
MVP
MVP被很多人解析為Model-View-Presenter,其實在我看來,MVP也是從MVC的基礎(chǔ)上衍生而來,主要還是一個面向協(xié)議的編程模式。模型層跟MVC里的模型層一致,處于一個數(shù)據(jù)模型建立以及數(shù)據(jù)處理的層級,但是視圖層就不會是單純的指一些基本視圖了,controller的視圖也應該歸納在視圖層。而我們自定義的Presenter則擔任起了絕大部分可以抽離出來的業(yè)務(wù)邏輯,controller只是控制簡單的視圖層級控制以及presenter的調(diào)度使用。presenter處理數(shù)據(jù)在模型層和視圖層的流通,模型層和視圖層不允許有任何直接交互。presenter和Controller通過代理的方式進行互相調(diào)用,視圖層和presenter也可以通過代理或者直接交互的方式進行數(shù)據(jù)流通,模型層對數(shù)據(jù)的更改和頁面的更新必須通過presenter來在頁面上進行展示。所以我們需要再配套一個協(xié)議文件以及獨立的網(wǎng)絡(luò)層來保證一個完整嚴謹?shù)腗VP架構(gòu)模式。話不多說,下面是我自己寫的一個MVP實例文件
效果


gif.gif

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


屏幕快照 2019-06-19 02.10.51.png

其中
service負責模擬一個異步的網(wǎng)絡(luò)數(shù)據(jù)請求
presenter則是作為主要的業(yè)務(wù)處理層級
controller負責presenter的創(chuàng)建和調(diào)度使用
view負責視圖及數(shù)據(jù)的更新展示

model負責數(shù)據(jù)的處理及作為數(shù)據(jù)的模型
TestPresenterProtocol 聲明頁面直接調(diào)用需要的代理方法,控制頁面直接交互的接口
該demo的基本功能是模擬數(shù)據(jù)加載,加載之后tableView里進行展示,可以通過cell里的加減數(shù)值按鈕修改對應的展示內(nèi)容,下拉刷新之后頁面數(shù)據(jù)重新加載回歸原始狀態(tài)。
經(jīng)過抽離之后,controller里的基本的代碼量如下:


屏幕快照 2019-06-19 02.29.47.png

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

TestPresenter里進行了控制器和視圖組件的綁定以及網(wǎng)絡(luò)數(shù)據(jù)的加載
屏幕快照 2019-06-19 02.35.37.png

處理tableView的代理邏輯和cell中的點擊事件處理邏輯
屏幕快照 2019-06-19 02.40.22.png

以及給tableView添加下拉刷新等操作。
不過要注意presenter對controller及其中的視圖引用都為弱引用,避免循環(huán)引用的產(chǎn)生。

controller則負責presenter的創(chuàng)建、調(diào)用及實現(xiàn)代理協(xié)議中定義的方法


屏幕快照 2019-06-19 02.42.15.png

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


屏幕快照 2019-06-19 02.43.21.png

還有我們的代理文件里的定義
TestPresenterProtocol 定義了cell的點擊代理以及controller展示/隱藏提示視圖的代理
屏幕快照 2019-06-19 02.44.47.png

這樣,我們就以MVP架構(gòu)實現(xiàn)了這樣一個簡單的需求。如果presenter里的業(yè)務(wù)量比較繁雜,我們可以將整個業(yè)務(wù)分為幾個小模塊,通過創(chuàng)建presenter的category來實現(xiàn)業(yè)務(wù)的進一步分層解耦,避免presenter文件過于臃腫的情況出現(xiàn)。

總結(jié): 其實在我看來,不同的開發(fā)者對于同一種設(shè)計架構(gòu)都有自己的理解,實現(xiàn)的方式也都不盡相同。并不是說對于某種架構(gòu)就一定有明確的文件夾路徑和定死的規(guī)范讓我們循規(guī)蹈矩的來使用,如果一板一眼的按照某種特定的方式來寫,那么了解更多的架構(gòu)模式反而會成為我們開發(fā)的累贅。重點是我們在設(shè)計自己項目的時候有意識的去使用這種思想來減輕我們的代碼冗余量和控制層的厚重程度,讓我們的項目變得結(jié)構(gòu)清晰簡潔、業(yè)務(wù)分層明了,便于他人閱讀和日后維護。而且合適的架構(gòu)并不一定意味著代碼量的減少,有時候反而代碼量和文件數(shù)會增加一些。但是采用新的架構(gòu)的原則一定是這個架構(gòu)可以讓我們的項目結(jié)構(gòu)變得清晰,維護成本大大降低。
完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。