MVC坤溃?
Model:表示業(yè)務(wù)數(shù)據(jù)對(duì)象拍霜;View:展現(xiàn)數(shù)據(jù)的 UI;Controller:Model 跟 View 之間的粘合劑薪介。一方面對(duì) View 上的行為作出反應(yīng)祠饺,通常會(huì)涉及到 Model 的更改;
另一方面將 Model 的改動(dòng)反映到 View 上汁政。由于 Controller 作為粘合劑的存在道偷,View 和 Model 只需要跟 Controller 交互,而不知道另一方的存在记劈。
這樣勺鸦,View 和 Model 作為獨(dú)立可復(fù)用的組件,Controller 里處理業(yè)務(wù)邏輯目木。聽起來這樣的架構(gòu)很清晰直觀换途,實(shí)際應(yīng)用中,MVC 對(duì)于不是很復(fù)雜的 App 也是非常高效的刽射。
但對(duì)稍復(fù)雜些的 App军拟,MVC 使用起來就會(huì)非常吃力。你可能聽過 MVC 也被簡(jiǎn)稱為 Massive View Controller誓禁,這就是原因所在 懈息。
View Controller 承擔(dān)的職責(zé)太多:網(wǎng)絡(luò)請(qǐng)求漓拾、數(shù)據(jù)訪問和存儲(chǔ)戒祠、UI 的調(diào)整和組合姜盈、業(yè)務(wù)邏輯馏颂、View 的 delegate救拉、data source亿絮、狀態(tài)的維護(hù)與單一責(zé)任準(zhǔn)則(Single Responsibility Principle
)背道而馳派昧。
過于臃腫的 View Controller 使 App 的維護(hù)成本非常高蒂萎。盡管把網(wǎng)絡(luò)請(qǐng)求以及數(shù)據(jù)訪問和存儲(chǔ)放到了 Model 里,但由于對(duì)象邊界的定義不夠清晰纳寂,大部分 View Controller 依然很臃腫烈疚,上千行的 View Controller 很常見爷肝。
關(guān)于 View Controller 有個(gè)準(zhǔn)則:如果一個(gè) View Controller 超過了 300 行代碼灯抛,那它一定做了責(zé)任范圍以外的事对嚼。
更不幸的是由于一些職責(zé)移交給 Model纵竖,導(dǎo)致 Model 也變得臃腫起來靡砌。原來唯一可以做 Unit Test 的 Model 現(xiàn)在測(cè)試也很困難通殃。
為解決 Massive View Controller 的問題画舌,MVVM曲聂、VIPER 等架構(gòu)應(yīng)運(yùn)而生朋腋。要發(fā)揮 MVVM 的優(yōu)勢(shì)乍丈,需要有 Reactive。Reactive 增加學(xué)習(xí)成本的同時(shí)忆矛,也讓調(diào)試變得更困難催训。
VIPER雖然能平衡責(zé)任的分配漫拭,但由于引入過多對(duì)象采驻,維護(hù)成本高礼旅。一個(gè)簡(jiǎn)單的頁面也要求新增多個(gè)類和大量傻瓜代碼痘系。
MVVM 汰翠?
MVVM 架構(gòu)是 MV(X) 里面最新的一個(gè)复唤,它的出現(xiàn)已經(jīng)考慮到了 MV(X) 模式之前所遇到的問題苟穆。
理論上來說雳旅,Model - View - ViewModel 看起來非常棒攒盈。View 和 Model 已經(jīng)很熟悉了型豁,中間人的角色VC控制器也很熟悉了迎变,但是在MVVM架構(gòu)里中間人的角色由VC控制器變成了ViewModel衣形。
iOS 里面的 ViewModel 到底是個(gè)什么東西呢谆吴?ViewModel能持有Model進(jìn)而修改Model句狼,同時(shí)能夠隨時(shí)監(jiān)聽Model的屬性數(shù)據(jù)變化進(jìn)而來開啟事件芜繁,因?yàn)閂iewModel同時(shí)持有View骏令,所以能夠在監(jiān)聽Model變化開啟的事件里直接對(duì)View進(jìn)行更新榔袋。
綁定View和ViewModel的方法包括:
1凰兑、使用基于 KVO 的綁定庫(kù)吏够,比如 RZDataBinding 或者 SwiftBond播急。
2桩警、使用全量級(jí)的函數(shù)式響應(yīng)編程框架,比如ReactiveCocoa捶枢、RxSwift 或者 PromiseKit烂叔。
實(shí)際上长已,現(xiàn)在提到「MVVM」你應(yīng)該就會(huì)想到 ReactiveCocoa术瓮,反過來也是一樣胞四。
雖然我們可以通過簡(jiǎn)單的綁定來實(shí)現(xiàn) MVVM 模式辜伟,但是 ReactiveCocoa(或者同類型的框架)會(huì)讓你更大限度的去理解 MVVM导狡。
當(dāng)然響應(yīng)式編程框架(FRF)也有一點(diǎn)不好的地方負(fù)責(zé)的地方就是學(xué)習(xí)曲線陡峭旱捧,如果半懂不懂很容易讓事情更糟枚赡。什么地方出錯(cuò)需要花費(fèi)更多的時(shí)間去調(diào)試贫橙。
MVP卢肃?
MVP(被動(dòng)變化的 View)莫湘,在 MVC 里面 View 和 Controller 是耦合緊密的逊脯,但對(duì) MVP 里面的 Presenter 來講军洼,它根本不關(guān)注 ViewController 的生命周期匕争,而且 View 也能被簡(jiǎn)單 mock 出來甘桑,所以在 Presenter 里面基本沒什么布局相關(guān)的代碼跑杭,它的職責(zé)只是通過數(shù)據(jù)和狀態(tài)更新 View德谅。
在 MVP 架構(gòu)里面窄做,UIViewController 的那些子類其實(shí)是屬于 View 的椭盏,而不是 Presenter掏颊。
這種區(qū)別提供了極好的可測(cè)性蚯舱,但是這是用開發(fā)速度的代價(jià)換來的枉昏,因?yàn)槟惚仨氁謩?dòng)的去創(chuàng)建數(shù)據(jù)和綁定事件兄裂。
MVP 架構(gòu)擁有三個(gè)真正獨(dú)立的分層晰奖,所以在組裝的時(shí)候會(huì)有一些問題匾南,而 MVP 也成了第一個(gè)披露了這種問題的架構(gòu)蛆楞。
因?yàn)槲覀儾幌胱?View 知道 Model 的信息豹爹,所以在當(dāng)前的 ViewController(角色其實(shí)是 View)里面去進(jìn)行組裝肯定是不正確的臂聋,我們應(yīng)該在另外的地方完成組裝孩等。
比如,我們可以創(chuàng)建一個(gè)應(yīng)用層(app-wide
)的 Router 服務(wù)冰垄,讓它來負(fù)責(zé)組裝和 View-to-View 的轉(zhuǎn)場(chǎng)播演。
這個(gè)問題不僅在 MVP 中存在写烤,在接下來要介紹的模式里面也都有這個(gè)問題洲炊。
MVP把大部分的職責(zé)都分配到了 Presenter 和 Model 里面暂衡,而 View 基本上不需要做什么(在上面的例子里面狂巢,Model 也什么都沒做)唧领。
可測(cè)性 - 簡(jiǎn)直棒,我們可以通過 View 來測(cè)試大部分的業(yè)務(wù)邏輯胯杭。
易用 - 就我們上面那個(gè)簡(jiǎn)單的例子來講做个,代碼量差不多是 MVC 架構(gòu)的兩倍居暖,但是 MVP 的思路還是蠻清晰的膝但。
MVP 架構(gòu)在 iOS 中意味著極好的可測(cè)性和巨大的代碼量。
還存在著另一種的 MVP - Supervising Controller MVP莺奸。這個(gè)版本的 MVP 包括了 View 和 Model 的直接綁定温学,與此同時(shí) Presenter(Supervising Controller)仍然繼續(xù)處理 View 上的用戶操作甚疟,控制 View 的顯示變化轧拄。
MVVM比較MVP檩电?
劃分性俐末。MVVM 框架里面的 View 比 MVP 里面負(fù)責(zé)的事情要更多一些卓箫。MVVM是通過 ViewModel 的數(shù)據(jù)綁定來更新View的數(shù)據(jù)烹卒,而MVP只是把所有的事件統(tǒng)統(tǒng)交給 Presenter 去進(jìn)行數(shù)據(jù)處理但并不負(fù)責(zé)更新View的數(shù)據(jù),這也是MVP無法很好使用FRP響應(yīng)式框架的原因馁筐。
可測(cè)性敏沉。在MVVM中盟迟, ViewModel 對(duì)View 是一無所知的潦闲,而View則完全依賴ViewModel處理數(shù)據(jù)并釋放出處理結(jié)果的信號(hào)辖众。因此MVVM的測(cè)試ViewModel里面的邏輯漏洞變得特別簡(jiǎn)單凹炸。
易用性啤它。在實(shí)際的應(yīng)用當(dāng)中 MVVM 會(huì)更簡(jiǎn)潔一些变骡。在 MVP 下你必須要把 View 的所有事件都交給 Presenter 去處理塌碌,而且需要手動(dòng)的去更新 View 的狀態(tài);而在 MVVM 下瓢捉,你只需要用使用KVO或是ReactiveCocoa綁定就可以解決泡态。而且MVVM 所有框架中唯一自動(dòng)更新視圖的框架桐汤,因?yàn)樵?View 上已經(jīng)做了數(shù)據(jù)綁定员萍,只要ViewModel持有的Model屬性的特定屬性值發(fā)送變化拣度,自動(dòng)就會(huì)發(fā)送數(shù)據(jù)數(shù)據(jù)變化的信號(hào)抗果,然后更新View上UI控件的值日麸。
VIPER 逮光?
VIPER框架不屬于任何一種 MV(X) 框架代箭。
MV(X) 本質(zhì)上是把模塊分成三層,X無論是什么睦霎,都是器管道紐帶連接的作用梢卸。
但是VIPER能夠具備更細(xì)的顆粒度走诞,將模塊分成了五層。
1、Interactor(交互器) 處理數(shù)據(jù)(Entities)或網(wǎng)絡(luò)請(qǐng)求的業(yè)務(wù)邏輯古毛。
2彰触、Presenter(展示器) 處理UI相關(guān)的業(yè)務(wù)邏輯,可以調(diào)用 Interactor 中的方法來屬性數(shù)據(jù)塞绿。3沟涨、Entities(實(shí)體)作為一純粹的數(shù)據(jù)對(duì)象只是用來規(guī)范數(shù)據(jù)和記錄數(shù)據(jù)。
4棋返、Router(路由)主要是聯(lián)系 VIPER各個(gè)模塊之間的紐帶射沟。
MV(X)對(duì)比VIPER ?
劃分性。Controller/Presenter/ViewModel 的職責(zé)里面准潭,只有 UI 的展示功能被轉(zhuǎn)移到了 Presenter 里面泼掠。Presenter 不具備直接更改數(shù)據(jù)的能力腻豌。但是毫無疑問的惹骂,VIPER 在職責(zé)劃分方面是做的最好的著拭,畢竟顆粒度分了五層。
可測(cè)性。理所當(dāng)然的,職責(zé)劃分的越好,測(cè)試起來就越容易柱搜。
易用性健爬。良好的職責(zé)劃分就意味著一個(gè)小小的任務(wù)设拟,可能就需要你為各種類寫大量的接口,維護(hù)代價(jià)直線升高。
后臺(tái)架構(gòu)?
服務(wù)器端開發(fā)常用Service Oriented Architecture服務(wù)導(dǎo)向架構(gòu)休里,把業(yè)務(wù)分成了多個(gè)邏輯獨(dú)立的組件。
一個(gè)組件相當(dāng)于一個(gè) Service做粤,封裝了與其業(yè)務(wù)相關(guān)的功能堵泽,如 UserService 負(fù)責(zé)用戶的注冊(cè)尤辱、登入等,而 BabyService 有 Baby 的增加圃酵、移除罗捎、以及數(shù)據(jù)的記錄等。
Service 是對(duì)整個(gè)架構(gòu)縱向邏輯切分的結(jié)果根悼。
拋開業(yè)務(wù)邏輯談 Service 意義不大,Service 通常與數(shù)據(jù)庫(kù)表的設(shè)計(jì)緊密相關(guān)糊肤。
橫向的邏輯切分將 Baby App iOS 的架構(gòu)自上而下切分成三個(gè)層(Layer):應(yīng)用層(Application Layer)拗踢、服務(wù)層(Service Layer)、數(shù)據(jù)層(Data Access Layer)陡叠。
服務(wù)層和數(shù)據(jù)層把復(fù)雜的邏輯封裝起來拙徽,作為 Framework 提供接口給上層調(diào)用裁眯。
應(yīng)用層只能調(diào)用服務(wù)層暴露出來的接口它改,而不能直接調(diào)用數(shù)據(jù)層抹凳。
層次結(jié)構(gòu)加強(qiáng)了可重用性和可測(cè)試性嘁扼。應(yīng)用層調(diào)用服務(wù)層提供的簡(jiǎn)單接口獲得數(shù)據(jù)或者實(shí)行用戶操作访娶。
服務(wù)層也不需要知道數(shù)據(jù)層中網(wǎng)絡(luò)請(qǐng)求割笙,服務(wù)器同步走净,以及數(shù)據(jù)持久化的具體實(shí)現(xiàn)蜓堕。
服務(wù)層息尺,數(shù)據(jù)層搂誉,以及應(yīng)用層都能很容易實(shí)現(xiàn)各自的單元測(cè)試(Unit Test)侮腹。Framework 是很棒的工具至非。把服務(wù)層和數(shù)據(jù)層打包成 Framework,不僅幫助構(gòu)建解耦可重用的代碼涨颜,同時(shí) App 的結(jié)構(gòu)和業(yè)務(wù)邏輯也更加清晰。
應(yīng)用層(Application Layer)應(yīng)用層也可以叫展示層(Presentation Layer)酒来,負(fù)責(zé) UI 跟 展示邏輯卢未。
從Code角度說,就是 UIView 跟 UIViewController 的集合堰汉。
復(fù)雜的邏輯都封裝到了下層辽社,UIViewController 就變得十分輕量。
View Controller主要負(fù)責(zé)三件事:
1翘鸭、從 Service 獲得數(shù)據(jù)(ViewModel)并展示
2滴铅、響應(yīng)用戶操作,調(diào)用相應(yīng)的 Service 接口
3就乓、監(jiān)聽 Service 層發(fā)出的消息汉匙,并執(zhí)行相應(yīng)操作,如更新 UI生蚁。
從 Service 獲取的 ViewModel 實(shí)例并不是 NSManagedObject 或者其他持久化的 model 實(shí)例噩翠,跟 MVVM 中的 ViewModel 也不一樣。
它只是簡(jiǎn)單的 Swift Struct邦投,提供應(yīng)用層需要的數(shù)據(jù)值伤锚。
使用 Struct 結(jié)構(gòu)體的好處主要是:
1、值類型(Value Type): 簡(jiǎn)單志衣、容易理解屯援,線程安全
2猛们、松耦合,減少 View Controller 之間可能的交互
3狞洋、減少了 Statefulness 和 Mutability似的應(yīng)用運(yùn)行更高效弯淘、占用內(nèi)存更少。
使用 Struct 也就意味著想要底層持久化 Model 的更改反映到 UI 上徘铝,你必須通過 Service 再抓一次數(shù)據(jù)耳胎。
也許有人認(rèn)為這是使用 Struct 的一個(gè)缺點(diǎn),其實(shí)不是惕它,這應(yīng)該是優(yōu)點(diǎn)。因?yàn)?Immutable 的 ViewModel 废登,讓 View Controller 變得更加簡(jiǎn)單淹魄,你不用擔(dān)心其他地方的代碼會(huì)更改你的 ViewModel 實(shí)例。
調(diào)試起來也會(huì)更加方便堡距,代碼更容易理解甲锡、可讀更高。
WWDC 中有好幾個(gè)視頻都對(duì) Struct 的使用和優(yōu)勢(shì)進(jìn)行了詳解羽戒。服務(wù)層(Service Layer)服務(wù)層定義了一系列 Service 和供給應(yīng)用層使用的 ViewModel缤沦。
Service 封裝了 App 主要的業(yè)務(wù)邏輯,負(fù)責(zé)把底層持久化的 Model 和網(wǎng)絡(luò)請(qǐng)求返回的 JSON 轉(zhuǎn)換為 ViewModel易稠,再提供給應(yīng)用層使用缸废。
這樣的分離即加強(qiáng)了 Immutablility 和 Statelessness,也讓應(yīng)用層中的 ViewController 更輕量驶社,只需幾行 Service calls企量。
Service 雖然承擔(dān)大部分業(yè)務(wù)邏輯,但一個(gè) Service 通常也就 300 行左右的代碼量亡电,這得益于數(shù)據(jù)層的封裝和抽象届巩。
數(shù)據(jù)層(Data Access Layer)的作用是提供簡(jiǎn)化的數(shù)據(jù)訪問接口,主要有 3 個(gè)模塊:數(shù)據(jù)存儲(chǔ)(Persistence
)份乒、網(wǎng)絡(luò)請(qǐng)求(Network
)恕汇、數(shù)據(jù)同步(Data Synchronization
)。
數(shù)據(jù)存儲(chǔ)我們使用的是 Core Data或辖,也可以用 Realm 或者其他數(shù)據(jù)庫(kù)代替瘾英。
網(wǎng)絡(luò)請(qǐng)求我們使用了 Moya 進(jìn)行抽象,使 API 的設(shè)計(jì)和調(diào)用更簡(jiǎn)潔孝凌,并支持我們 Server 自定義的錯(cuò)誤方咆。數(shù)據(jù)同步模塊,會(huì)自動(dòng)同步本地和服務(wù)器端的用戶數(shù)據(jù)蟀架。
MVC 因 Controller 的臃腫而遭到眾人詬病瓣赂。但其實(shí) MVC 作為最基礎(chǔ)的設(shè)計(jì)模式榆骚,展現(xiàn)了一個(gè)架構(gòu)的精髓 - 抽象分離。
從整體看煌集,數(shù)據(jù)層是 Model妓肢,業(yè)務(wù)層是 Controller,應(yīng)用層是 View苫纤。
如果看細(xì)節(jié)的地方碉钠,應(yīng)用層跟也務(wù)層提供的 ViewModel 也可以看做一個(gè) MVC:ViewModel - UIViewController - UIView.
單例?
單例作為Cocoa框架中被廣泛使用的核心設(shè)計(jì)模式之一卷拘。事實(shí)上喊废,蘋果開發(fā)者庫(kù)把單例作為 "Cocoa 核心競(jìng)爭(zhēng)力" 之一。應(yīng)用中我們經(jīng)常和單例打交道栗弟,比如 UIApplication 和 NSFileManager 等等污筷。我們?cè)陂_源項(xiàng)目、蘋果示例代碼和 StackOverflow 中見過了無數(shù)使用單例的例子乍赫。但這也不可避免地產(chǎn)生了不好的影響:
1瓣蛀、全局狀態(tài)
使用全局可變的狀態(tài)使得程序變得難以理解,難以調(diào)試雷厂。因?yàn)樵诿嫦驅(qū)ο缶幊痰囊淮蠡驹瓌t就是惋增,對(duì)于可變的變量狀態(tài)而言,作用域越小越能避免一個(gè)員工兩個(gè)老板的誤會(huì)改鲫。
2诈皿、隱式耦合
程序的任意模塊都可以訪問單例意味著任何和這個(gè)單例交互產(chǎn)生的副作用都會(huì)影響程序其他地方的任意代碼。這就表示即使是兩個(gè)完全獨(dú)立的模塊钩杰。也會(huì)因?yàn)閱卫峁┑墓蚕頎顟B(tài)而產(chǎn)生副作用影響纫塌。由于單例具有全局和多狀態(tài)的特性,導(dǎo)致隱式地在兩個(gè)看起來完全不相關(guān)的模塊之間建立了耦合讲弄。
3措左、單元測(cè)試
一個(gè)全局屬性變量的值長(zhǎng)期存在不消失(持久化狀態(tài))會(huì)嚴(yán)重干擾單元測(cè)試。比如說避除,A和B是兩個(gè)不同的模塊怎披,但是都共同持有了一個(gè)單例對(duì)象的屬性值,這時(shí)候就不滿足單元測(cè)試的前提條件瓶摆,兩個(gè)模塊相互獨(dú)立凉逛。
4、生命周期
在程序中添加一個(gè)單例時(shí)群井, “永遠(yuǎn)只會(huì)有一個(gè)實(shí)例”的原則可能會(huì)被打破状飞。例如賬號(hào)注銷用戶切換,舊用戶的存儲(chǔ)在全局單例之中的所有狀態(tài)數(shù)據(jù)都必須清理掉。同時(shí)也希望登錄的新用戶能夠使用一個(gè)全新的全局單例诬辈,而不是簡(jiǎn)單的刪除單例的數(shù)據(jù)值酵使。那么就必須在用戶注銷的時(shí)候置空舊用戶的單例類對(duì)象,新用戶登錄的時(shí)候?qū)嵗粋€(gè)新的單例類對(duì)象焙糟。但是假如說現(xiàn)在用戶單例類正在存儲(chǔ)異步下載的圖片口渔,如果突然置空舊用戶的單例類對(duì)象會(huì)造成繼續(xù)下載的圖片保留到了新用戶的單例里。所以必須保證在置空舊用戶的單例類對(duì)象之前將異步下載圖片的操作給關(guān)閉掉穿撮。但是問題就在于置空舊用戶的單例類對(duì)象時(shí)你很難準(zhǔn)確地判斷出單例實(shí)例的所有者(因?yàn)閱卫约汗芾碜约旱纳芷?缺脉,準(zhǔn)確判斷出“關(guān)閉”的單例是一個(gè)舊用戶的單例將變得非常的困難。
雜談悦穿?
雀圣和菜鳥正在觀看同一個(gè)人打麻將攻礼,雀圣能夠察覺到的內(nèi)容會(huì)遠(yuǎn)遠(yuǎn)超過新手,并非雀圣火眼金睛咧党,而是掌握了無形的武器秘蛔,通過建立一整套思維抽象,雀圣能夠透過現(xiàn)象看到本質(zhì)傍衡,把對(duì)原始現(xiàn)象的感知轉(zhuǎn)換成對(duì)目前局勢(shì)簡(jiǎn)明扼要的理解。雀圣在看到牌局的一瞬間负蠕,就會(huì)聯(lián)想到某種進(jìn)攻戰(zhàn)術(shù)的成功蛙埂。這就叫觀察能力。
最好學(xué)些架構(gòu)的方式就是先經(jīng)過照貓畫虎式的時(shí)實(shí)踐遮糖,然后系統(tǒng)地培訓(xùn)設(shè)計(jì)方法绣的。
同一個(gè)應(yīng)用里面,即便有幾種混合的架構(gòu)模式也是很正常的一件事情欲账。比如:開始的時(shí)候屡江,你用的是 MVC 架構(gòu),后來你意識(shí)到有一個(gè)特殊的頁面用 MVC 做的的話維護(hù)起來會(huì)相當(dāng)?shù)穆闊┤唬贿@個(gè)時(shí)候你可以只針對(duì)這一個(gè)頁面用 MVVM 模式去開發(fā)惩嘉,對(duì)于之前那些用 MVC 就能正常工作的頁面,你完全沒有必要去重構(gòu)它們踢故,因?yàn)閮煞N架構(gòu)是完全可以和睦共存的
MVVM 配合綁定機(jī)制效果最好文黎,這個(gè)綁定機(jī)制就是RAC,使用MVC只能感受到RAC的部分好處殿较。一說到綁定耸峭,自然就是想到了 KVO(Key-Value Observation)。然而淋纲,對(duì)于一個(gè)簡(jiǎn)單的綁定都需要很大的樣板代碼劳闹,更不用說有許多屬性需要綁定了。所以ReactiveCocoa橫空出世,雖然MVVM 并未強(qiáng)制我們使用 ReactiveCocoa本涕。但是MVVM 在良好的綁定框架下更能釋放出潛力业汰。
模塊如何劃分?
桌面負(fù)責(zé)模擬控制臺(tái)和狀態(tài)顯示偏友、嵌入式負(fù)責(zé)設(shè)備控制和狀態(tài)數(shù)據(jù)讀取開發(fā)技術(shù)如何選型
如何適應(yīng)可能發(fā)生的變化蔬胯、交互機(jī)制
如何設(shè)計(jì)程序?
看別人的設(shè)計(jì)成果位他,體會(huì)別人的設(shè)計(jì)過程氛濒,試著自己來設(shè)計(jì)。討論功能需求鹅髓、討論非功能需求中的質(zhì)量屬性需求舞竿、解決待完成任務(wù)的限制條件的約束需求。