MVC
- 經(jīng)典就是經(jīng)典媒峡,沒(méi)有之一。iOS中MVC架構(gòu)袱蜡,看懂斯坦福大學(xué)白胡子老頭這張圖基本上就可以了丝蹭。
- 簡(jiǎn)單理解,就是Controller對(duì)象擁有View和Model對(duì)象,兩者通過(guò)Controller進(jìn)行溝通奔穿。對(duì)于單個(gè)頁(yè)面镜沽,三個(gè)類就搞定了,感覺(jué)很簡(jiǎn)單贱田。
網(wǎng)絡(luò)連接應(yīng)該放在哪里缅茉?Model中嗎?感覺(jué)很有道理男摧?實(shí)際上蔬墩,很多的網(wǎng)絡(luò)連接的發(fā)起和接收后的處理都放在了Controller中,因?yàn)榉奖懵锖耐亍odel一般只有屬性定義拇颅,沒(méi)有實(shí)現(xiàn)。
View應(yīng)該是獨(dú)立一塊了吧乔询?實(shí)際上呢樟插,View大多都放在了Controller中,有個(gè)loadView函數(shù)竿刁,很方便啊黄锤。有幾個(gè)人會(huì)單獨(dú)寫(xiě)個(gè)類來(lái)作為view?
本來(lái)食拜,xib和Storyboard是很好的分離view的方式鸵熟。但是,由于“不合適多人合作负甸,版本管理”流强,非要代碼寫(xiě)界面,還振振有詞:“性能高惑惶,對(duì)培養(yǎng)新人有好處”煮盼。“謊言說(shuō)100次都能成真話”带污,何況這些理由聽(tīng)上去還那么有理僵控。
像“檢查用戶名是否合法,檢查密碼對(duì)不對(duì)”應(yīng)該放在哪里呢鱼冀?有幾個(gè)人會(huì)像斯坦福大學(xué)白胡子老頭那樣新起一個(gè)類來(lái)寫(xiě)报破?基本上都是Controller中搞定。
BaseController千绪,BaseView充易,BaseModel一定見(jiàn)過(guò)不少吧?有的還有好幾層呢
公共View荸型,各種名字帶common或者類似的類常見(jiàn)吧盹靴?里面網(wǎng)絡(luò)連接,數(shù)據(jù)庫(kù),邏輯等等往往比View本身很多稿静,儼然一個(gè)小模塊了梭冠,功能比Controller都強(qiáng)大了。這還是view嗎改备?
本來(lái)MVC理論上是最簡(jiǎn)單的架構(gòu)控漠,但是實(shí)際結(jié)果呢,變成了最難懂的架構(gòu)悬钳。Controller成了上帝類盐捷,什么都干∧矗“只知道那一坨東西有用碉渡,但看不出那是簡(jiǎn)單的MVC”。
MVC也被稱之為 Massive View Controller(重量級(jí)視圖控制器)灾测。其實(shí)這不是MVC的錯(cuò)爆价,只是沒(méi)有程序員承認(rèn)自己懶惰,編程習(xí)慣不好罷了媳搪。如果能夠像斯坦福大學(xué)白胡子老爺爺那樣好的編程習(xí)慣,那么大部分的iOS程序都能有清晰的MVC架構(gòu)骤宣。
MVVM
認(rèn)識(shí)MVVM的起點(diǎn)是@objc上文章MVVM 介紹
MVVM來(lái)自MVC秦爆,一張經(jīng)典的圖就是下面這張,在好多文章中看到過(guò)憔披。
“稍微考慮一下等限,雖然 View 和 View Controller 是技術(shù)上不同的組件,但它們幾乎總是手牽手在一起芬膝,成對(duì)的望门。你什么時(shí)候看到一個(gè) View 能夠與不同 View Controller 配對(duì)?或者反過(guò)來(lái)锰霜?所以筹误,為什么不正規(guī)化它們的連接呢?” ------ 這段話當(dāng)時(shí)給我的印象很深刻癣缅,這個(gè)觀點(diǎn)到現(xiàn)在我都認(rèn)可厨剪。
Controller代表了一個(gè)場(chǎng)景(Scene)的生命周期,是一個(gè)調(diào)度者友存。什么都是祷膳,因?yàn)槭裁炊茧x不開(kāi)它。又好像什么都不是屡立,因?yàn)樗聿涣巳魏尉唧w的東西直晨。讓它和View合在一起,作為廣義的view就有了具體的意義,并限制了它無(wú)所不能的印象勇皇。這點(diǎn)值得肯定奕巍。
“顯示邏輯(presentation logic)”可以從Controller中移到ViewModel中,從而給Controller減負(fù)儒士。這個(gè)觀點(diǎn)我也是支持的的止。并且我以此認(rèn)為ViewModel就是用來(lái)做“顯示邏輯”的,一個(gè)頁(yè)面一個(gè)着撩,隨頁(yè)面而變化诅福。在Swift中,我用結(jié)構(gòu)體來(lái)做ViewModel拖叙。
關(guān)于綁定機(jī)制氓润,文章中推薦ReactiveCocoa。我去大致看了一下薯鳍,主要是將KVO拉庶,Block,notification珠漂,delegate等各種通訊機(jī)制統(tǒng)一為RACSignal父款,將界面和數(shù)據(jù)進(jìn)行雙向綁定,功能確實(shí)強(qiáng)大斩松。但是這個(gè)風(fēng)格和普通iOS的開(kāi)發(fā)習(xí)慣差距比較大伶唯,一下子很難變過(guò)來(lái)。文章中也說(shuō)只是推薦惧盹,不強(qiáng)求乳幸,所以我也就一直沒(méi)有采用。被誤解的MVC和被神化的MVVM
至于綁定機(jī)制钧椰,在Swift中可以使用屬性觀察者粹断。ViewModel一般作為Controller的一個(gè)屬性,對(duì)它進(jìn)行觀察嫡霞,一旦變化瓶埋,就用ViewModel新的值設(shè)置界面元素,感覺(jué)挺好用的秒际。還有一些相隔很遠(yuǎn)或者一對(duì)多的變化悬赏,一般可以采用NSNotification來(lái)達(dá)到目的。
從網(wǎng)絡(luò)取數(shù)據(jù)娄徊,業(yè)務(wù)邏輯(相對(duì)于顯示邏輯)闽颇,應(yīng)該放在那里呢?文章中沒(méi)有說(shuō)寄锐,看意思是保留在Controller中兵多。還有的觀點(diǎn)認(rèn)為應(yīng)該放在ViewModel中尖啡,這當(dāng)然有道理,而且這是主流的理解剩膘。但是這樣會(huì)讓ViewModel變成另外一個(gè)上帝類衅斩。
我理解的MVVM
這是本人的理解,僅僅一家之言怠褐。主流的觀點(diǎn)沒(méi)有Logic那個(gè)類畏梆,從圖中刪除基本上就是了。ViewModel將是替代Controller的一個(gè)上帝類奈懒。
Controller主要作為調(diào)度者奠涌,居于中心位置×仔樱客串部分View相關(guān)功能:比如動(dòng)畫(huà)里面關(guān)于view的位置改變溜畅,這些代碼是要放在Controller里面的。這也符合Controller+View實(shí)現(xiàn)view功能的概念极祸。
ViewModel專門(mén)做“顯示邏輯”慈格,并且用屬性觀察者做綁定,必要的時(shí)候用Notification遥金。正向的綁定比如“action-target”響應(yīng)就保留在Controller中浴捆,具體事情交個(gè)其他類做就可以了。
在Swift中汰规,ViewModel和Model推薦用Struct汤功;Logic傾向于用class。從一個(gè)簡(jiǎn)單直觀的概念來(lái)說(shuō)溜哮,ViewModel需要保持輕量級(jí),跟隨頁(yè)面走色解,隨時(shí)準(zhǔn)備修改茂嗓。Model也是輕量級(jí),跟隨后臺(tái)API定義走科阎,只是個(gè)數(shù)據(jù)結(jié)構(gòu)述吸,隨時(shí)準(zhǔn)備修改。而Logic就顯得比較大锣笨,考慮穩(wěn)定蝌矛,考慮復(fù)用。
增加Logic類错英,負(fù)責(zé)業(yè)務(wù)邏輯入撒,比如從網(wǎng)絡(luò)取數(shù)據(jù),修改數(shù)據(jù)庫(kù)椭岩,檢查用戶名合法性茅逮,具體的響應(yīng)邏輯璃赡,監(jiān)聽(tīng)后的具體處理等等
猿題庫(kù) iOS 客戶端架構(gòu)設(shè)計(jì)
文章中的DataController相當(dāng)于這里的Logic
重點(diǎn)是Controller減負(fù),盡量起調(diào)度者職能献雅,具體工作都放到Logic中處理碉考。
Logic考慮復(fù)用,可以對(duì)應(yīng)單個(gè)頁(yè)面挺身,也可以多個(gè)頁(yè)面共用侯谁。按照業(yè)務(wù)邏輯的思路去劃分模塊。劃分標(biāo)準(zhǔn)可以和頁(yè)面分類標(biāo)準(zhǔn)不一樣章钾。
對(duì)于復(fù)雜頁(yè)面墙贱,View和ViewModel可以多個(gè),按照組件的模式去考慮伍玖。
對(duì)于表格嫩痰,ViewModel對(duì)應(yīng)的是表格的cell,dataSource數(shù)組中放ViewModel的序列窍箍。
表格的delegate和dataSource串纺,目前來(lái)看,放在Controller中是最方便的椰棘。當(dāng)然纺棺,為了給Controller減負(fù),再新增一個(gè)類TableDelegate也是很不錯(cuò)的方法邪狞。
如果表格包含在一個(gè)組件中祷蝌,用容器view做delegate和dataSource是一個(gè)不錯(cuò)的選擇。
主要想法就是想方設(shè)法“架空”Controller帆卓,讓它只做一個(gè)調(diào)度者巨朦,管理頁(yè)面的生命周期就可以了。實(shí)在非它不可的時(shí)候剑令,才讓它做具體的事情糊啡。
不引入ReactiveCocoa等龐大的第三方庫(kù)。這里有一篇文章不錯(cuò)吁津,值得學(xué)習(xí)一下棚蓄。
ReactiveCocoa 和 MVVM 入門(mén)
VIPER
這是比MVVM分類更細(xì)的一種模式。
經(jīng)典圖形
View: 也是View + Controller
Present:相當(dāng)于ViewModel碍脏,叫展示器
Interactor:交互器梭依,側(cè)重于業(yè)務(wù)邏輯;從網(wǎng)絡(luò)取數(shù)據(jù)典尾,數(shù)據(jù)庫(kù)等功能都在這里役拴。
Entity:就是Model,僅僅是數(shù)據(jù)定義
WireFrame:就是Router急黎,是頁(yè)面跳轉(zhuǎn)
值得借鑒的地方
將頁(yè)面跳轉(zhuǎn)獨(dú)立出來(lái)扎狱,做成公共模塊
將業(yè)務(wù)邏輯獨(dú)立出來(lái)侧到,做成公共模塊
如果只是對(duì)于單個(gè)頁(yè)面,分這么多類淤击,感覺(jué)有點(diǎn)啰嗦了匠抗。不夠?qū)τ诙囗?yè)面的模塊來(lái)說(shuō),還是有借鑒意義的
參考文章
其他架構(gòu)
一些實(shí)際在用污抬,但是沒(méi)有通用縮寫(xiě)名稱的架構(gòu)
分層模式
將服務(wù)service的概念引入客戶端汞贸,作為一個(gè)中間層,進(jìn)行隔離
業(yè)務(wù)邏輯作為服務(wù)模塊印机,通過(guò)服務(wù)的方式進(jìn)行訪問(wèn)
數(shù)據(jù)訪問(wèn)跟業(yè)務(wù)邏輯矢腻,表現(xiàn)層分開(kāi),是跟后臺(tái)的接口層
表現(xiàn)層只關(guān)注UI射赛,相當(dāng)于VIPER中的V和P多柑;或者是MVVM中的ViewModel(僅顯示邏輯)和View,但是沒(méi)有雙向綁定楣责;
平臺(tái)模式
當(dāng)前的APP竣灌,大多數(shù)是Native和H5的混合,將兩者的接口代碼統(tǒng)一成通用模塊是比較好的做法
插件化也是一個(gè)越來(lái)越普遍的趨勢(shì)秆麸,比如分享初嘹,第三方登錄,支付等等沮趣,都由第三方以插件的形式提供屯烦。對(duì)這些插件集中管理也是好的做法
APP隨著公司發(fā)展壯大,分出不同的事業(yè)部房铭,在同一公司多個(gè)APP或者不同業(yè)務(wù)驻龟;這樣就有兩個(gè)相反的發(fā)展趨勢(shì):一方面,想共用模塊缸匪,讓多個(gè)業(yè)務(wù)共享迅脐;另一方面,各自業(yè)務(wù)又要隔離豪嗽,獨(dú)立發(fā)展。公司也有可能成立公共的平臺(tái)部豌骏。各業(yè)務(wù)部門(mén)之間是縱向拆分龟梦;業(yè)務(wù)和平臺(tái)之間是橫向拆分。這就導(dǎo)致二維劃分的立體架構(gòu)窃躲。
參考文章
分離出界面層计贰,盡量薄,和UI同學(xué)協(xié)作蒂窒,快速應(yīng)變
分離數(shù)據(jù)層躁倒,盡量薄荞怒,與后臺(tái)合作,快速應(yīng)變
一些思考
架構(gòu)設(shè)計(jì)沒(méi)有統(tǒng)一的標(biāo)準(zhǔn)秧秉,上面接觸到的架構(gòu)模型褐桌,都有積極的參考意義,但是都不能照搬象迎。需要根據(jù)自己的實(shí)際情況進(jìn)行一定的權(quán)衡取舍
Step0:平臺(tái)型應(yīng)用
以URL的方式荧嵌,由主App調(diào)用子App
形式類似于調(diào)用打電話,發(fā)短信砾淌,發(fā)郵件
URL的定義需要統(tǒng)籌考慮
Step1:縱向劃分
分Native,H5汪厨,插件三部分
Native和H5之間提供統(tǒng)一的橋接模塊
Native和插件之間提供統(tǒng)一的橋架模塊
如果加入ReactNative赃春,那么也要提供Native和ReactNative之間的橋接模塊劫乱。這個(gè)可以先預(yù)留脱惰,也可以以后再添加。
Step2:橫向劃分
Native部分進(jìn)行橫劃分拉一,因?yàn)檫@一塊是最耗資源的部分
最上層是界面層(名字可以叫表現(xiàn)層或者UI層)蔚润,這里可以借用MVVM的思想磅氨。M不用考慮,由下層以服務(wù)的形式提供嫡纠。VM僅僅做顯示邏輯烦租,在Swift中用struct。這一層是跟產(chǎn)品的交流層除盏,盡量薄叉橱,并且能夠快速應(yīng)對(duì)變化。業(yè)務(wù)邏輯等能分出去的功能者蠕,一律分出去窃祝。核心和重點(diǎn)就是讓Controller只做調(diào)度者,萬(wàn)不得已可以酌情參與很少一部分的view工作踱侣。
最底層是微服務(wù)層(micro service)粪小。這一層提供基本的功能大磺,比如網(wǎng)絡(luò),緩存探膊,加解密杠愧,系統(tǒng)信息,日志突想,統(tǒng)計(jì)等等殴蹄。微服務(wù)的概念是只能供其他模塊調(diào)用,不能調(diào)用其他模塊的服務(wù)猾担。本層中的模塊之間也不能相互調(diào)用袭灯。這里是一些基礎(chǔ)的組件,按照功能劃分绑嘹,相互間的隔離是第一考慮要素稽荧。要求高內(nèi)聚。
中間是服務(wù)層(service)工腋,這里的服務(wù)可以調(diào)用微服務(wù)姨丈,也可以相互之間調(diào)用。
分三層相對(duì)簡(jiǎn)單一點(diǎn)擅腰。當(dāng)然也可以分出一些接口層蟋恬,服務(wù)層還可以分出公共服務(wù)層,業(yè)務(wù)邏輯層等趁冈。這個(gè)可以根據(jù)需要靈活配置歼争。但是總體上分三層(界面、服務(wù)渗勘、微服務(wù))沐绒。
不要跨層調(diào)用,界面層只能調(diào)用服務(wù)層提供的服務(wù)旺坠。服務(wù)層可以自己完成工作乔遮,也可以調(diào)用其他服務(wù)或者微服務(wù)完成工作。
Step3:層內(nèi)劃分
界面層:按照頁(yè)面進(jìn)行組織取刃,提供公共的UI組件蹋肮,可以理解為(M)VVM。VM作為將“界面顯示”轉(zhuǎn)換為“數(shù)據(jù)操作”的媒介璧疗,利用Swift的屬性觀察者特性括尸,進(jìn)行一級(jí)綁定。不引入RxSwift等函數(shù)式編程的大型第三方庫(kù)病毡。
服務(wù)層:分為公共服務(wù),跳轉(zhuǎn)邏輯屁柏,業(yè)務(wù)邏輯等模塊啦膜,按照邏輯功能劃分有送。跟具體頁(yè)面不必相關(guān),跟界面層的接口為各ViewModel種定義的協(xié)議僧家。
微服務(wù)層:按照功能劃分雀摘,不設(shè)計(jì)業(yè)務(wù)邏輯,分網(wǎng)絡(luò)八拱、數(shù)據(jù)庫(kù)阵赠、加解密,日志肌稻,統(tǒng)計(jì)等功能
框架圖
語(yǔ)言選擇Swift清蚀,最低支持版本iOS8,有條件的從iOS9開(kāi)始
服務(wù)和微服務(wù)都以framework的形式提供爹谭,模塊間的隔離需要重點(diǎn)考慮枷邪。
服務(wù)service和微服務(wù)僅僅是邏輯上的層次結(jié)構(gòu),在具體的工程組織上诺凡,都采用一級(jí)framework封裝东揣,相互間的層級(jí)和調(diào)用采用相互間的依賴隱含表示。
提供一個(gè)界面隔離的service.framework腹泌,界面層只調(diào)用它完成所有任務(wù)嘶卧。作用相當(dāng)于Foundation。
以workspace的方式組織工程凉袱,第三方管理工具采用Carthage芥吟。有條件的情況下,微服務(wù)以及部分服務(wù)可以采用私有Carthage的形式绑蔫,更方便復(fù)用运沦。
插件也要求以framework的形式提供,不接受.a的靜態(tài)庫(kù)配深。
如果暫時(shí)需要用到Object-C携添、C、C++篓叶,都統(tǒng)一成framework的形式烈掠,以后逐步用Swift替換。
類圖
界面層
AppDelegate缸托、ViewController僅僅作為調(diào)度者存在左敌,不做任何具體的事情。
ViewModel僅僅做顯示邏輯相關(guān)的事情俐镐,僅僅起到將界面轉(zhuǎn)換為數(shù)據(jù)的作用矫限。用結(jié)構(gòu)體struct,每個(gè)成員都是普通變量,并且都有默認(rèn)值叼风,代表了頁(yè)面的確定性取董。ViewModel是一種數(shù)據(jù)結(jié)構(gòu),做顯示邏輯的事情无宿。
UI組件僅由View和ViewModel組成茵汰,只能包含顯示邏輯,不能包含跳轉(zhuǎn)邏輯孽鸡,業(yè)務(wù)邏輯等蹂午。
ViewModel放UI層和Service層都有一定道理:放UI層表示顯示邏輯;放service層表示UI和service之間的數(shù)據(jù)接口彬碱《剐兀考慮再三,覺(jué)得還是應(yīng)該放UI層堡妒。ViewModel最大的作用還是在于將UI變化轉(zhuǎn)變?yōu)閿?shù)據(jù)操作配乱,本質(zhì)上離UI應(yīng)該更近一些。
至于UI層和Service層之間的接口皮迟,還是定義相關(guān)的的ViewModel protocol比較好搬泥。這些protocol的定義放在service中(由于framework的影響),將ViewModel的一些基本需求放在protocol中伏尼。service中用Model或者還是用其他來(lái)滿足這個(gè)protocol忿檩,就不做要求了。用protocol作為接口比單純用Model做接口要好爆阶。因?yàn)镸odel隨著后臺(tái)API定義而變燥透,而protocol只相當(dāng)于一個(gè)基類,類型更靈活辨图。
除了定義一個(gè)ViewModel的protocol之外班套,再定義一個(gè)是service的protocol,(既然引入service概念故河,就可以淡化logic和data的概念)吱韭。
界面層保持最輕量級(jí)。頁(yè)面跳轉(zhuǎn)邏輯鱼的,具體業(yè)務(wù)邏輯等工作全部下沉到服務(wù)層來(lái)做理盆。
ViewModel是一個(gè)struct,主要做顯示邏輯凑阶,概念相對(duì)比較小猿规,一般一個(gè)頁(yè)面一個(gè)或者多個(gè)。而service是一個(gè)類宙橱,概念比較大姨俩,可以多個(gè)頁(yè)面共用一個(gè)蘸拔。尺度可以根據(jù)具體情況靈活掌握。不同的頁(yè)面哼勇,通過(guò)擴(kuò)展遵循不同的協(xié)議區(qū)分開(kāi)來(lái)都伪。類本身可能比較大,但是每一部分都是相對(duì)比較小的积担。
服務(wù)層
service.framework作為一個(gè)粘合層存在,AppDelegate猬仁、ViewController只要import service就可以調(diào)用相關(guān)服務(wù)了帝璧。
金融.framework、保險(xiǎn).framework等屬于業(yè)務(wù)特有的邏輯
Router湿刽、用戶的烁、分享等屬于業(yè)務(wù)無(wú)關(guān)的公共邏輯
層內(nèi)的各模塊間可以相互調(diào)用
具體存在形式,單例诈闺、類渴庆、或者framework等,可根據(jù)具體情況靈活決定雅镊。
為了結(jié)構(gòu)清晰襟雷,圖中的調(diào)用關(guān)系線只畫(huà)出了很少的一部分,大部分線都沒(méi)有畫(huà)出來(lái)仁烹。
微服務(wù)層
從開(kāi)發(fā)的角度耸弄,按照功能分類;是工程師之間交流的技術(shù)語(yǔ)言卓缰,而不是跟產(chǎn)品交流的業(yè)務(wù)語(yǔ)言
功能高內(nèi)聚计呈,作為被調(diào)用的基礎(chǔ)模塊
模塊之間不要存在相互調(diào)用的關(guān)系
以framework的形式存在。高內(nèi)聚征唬,高復(fù)用捌显。