- Who supposed to own networking request: a Model or a Controller?
( On apps requiring usage of an API, you have network access code, too. Sometimes that code is separate from the model and sometimes it’s included within the model. It’s never included in the view and should not be included in the controller if avoidable (it’s always avoidable).) - How do I pass a Model into a View Model of a new View?
- MVC與MVVM的異同送滞。
- RAC在MVVM中扮演的角色。
- 如果轉(zhuǎn)變MVC為MVVM舆绎。
關(guān)于設(shè)計模式演化(其實是不斷增多的需求來推動的)
- 機(jī)器指令杉编,簡單的命令行炫刷,最早的程序是匯編
script 像腳本一樣開發(fā)飒硅。功能單一运沦、管理單一击孩、入口單一 - 計算機(jī)被用于科學(xué)計算,產(chǎn)生了高級語言挟冠,當(dāng)時的碼農(nóng)大多數(shù)是數(shù)學(xué)家
程序只是輸入輸出的過程 - 隨著計算機(jī)的發(fā)展于购,開始做的事情多了,出現(xiàn)軟件的概念
軟件的入口從指代Main函數(shù)編程了指代起始UI - 更高的UI需求知染,代碼被分為描述什么(業(yè)務(wù)邏輯)和有什么(UI)兩部分肋僧。
code block 很好處理邏輯與UI在一起的關(guān)系,在同一個地方可以同時維護(hù)UI合邏輯
code behind 能很好地處理UI和邏輯各自分開的關(guān)系,你可以讓UI和邏輯各自做好各自的事情
當(dāng)需求變得龐大嫌吠,解決方案也會變得龐大伪窖;當(dāng)解決方案變得龐大,就會出現(xiàn)細(xì)分居兆,出現(xiàn)細(xì)分,就會出現(xiàn)按哪種方式管理的問題竹伸。軟件從處理一件事務(wù)發(fā)展到了要處理許多事物泥栖。各事務(wù)間有包含、順序勋篓、主次等關(guān)系吧享。變得越來越復(fù)雜。因為數(shù)據(jù)與邏輯龐大了譬嚣,所以數(shù)據(jù)與邏輯就分離了钢颂。然后事件和業(yè)務(wù)分離了。
大致分為:界面拜银、數(shù)據(jù)殊鞭、事件、業(yè)務(wù)尼桶。
針對iOS來說(why care about choosing the architecture)
如果不使用合適的軟件架構(gòu)操灿,那么終會有一天,你需要調(diào)試一個非常龐大泵督,包含了眾多業(yè)務(wù)邏輯的類趾盐,那時你就會發(fā)現(xiàn)自己根本無從下手。通常來說小腊,開發(fā)者無法記住一個龐大的類的所有業(yè)務(wù)邏輯救鲤,因此在分析過程中,往往會因為類的內(nèi)容過多而忽略掉很多重要的細(xì)節(jié)秩冈。
- This class is the UIViewController subclass.
- Your data stored directly in the UIViewController
- Your UIViews do almost nothing
- The Model is a dumb data structure
- Your Unit Tests cover nothing
(即使我們是聽從了蘋果官方開發(fā)的MVC模式本缠,也會出現(xiàn)以上的問題。但是官方的MVC是有問題的漩仙。我們稍后會講到搓茬。)
怎樣才是好的設(shè)計模式呢(what's a good architecture)
- Balanced distribution of responsibilities among entities with strict roles.(單一職責(zé)、平衡職責(zé)队他、分工明確)
良好的分工可以讓我們更好的理解代碼卷仑。大腦對復(fù)雜問題的處理總是有極限的,所以處理復(fù)雜問題最好的辦 法是遵循單一的職責(zé)的原則麸折。 - Testability usually comes from the first feature (and don’t worry: it is easy with appropriate architecture).(良好的可測試性)
單元測試其實我不太熟悉锡凝,但是,對于深知單元測試好處的開發(fā)者來說垢啼。這并不是一個問題窜锯。單元測試可以大大的減少程序運行時才能發(fā)現(xiàn)的問題张肾。這樣可以節(jié)省 用戶反饋->bug修復(fù)->新版本發(fā)布=>用戶安裝新版本這個耗時長達(dá)一周乃止以上的過程。This means the tests saved those developers from finding issues in runtime, which might happen when an app is on a user’s device and the fix takes a week to reach the user. - Ease of use and a low maintenance cost.(易用性锚扎,低維護(hù)成本)
The best code is the code that has never been written.therefore the less code you have ,the less bugs you have.
MV(X)
View: 界面
Model:數(shù)據(jù)
Controller/Presenter/Viewmodel:the glue or mediator between M and V.
- 有利于我們更好的理解各個部分的功能和指責(zé)吞瞪,并且降低了各模塊間的耦合度。
- 使得View和Model更好復(fù)用驾孔。
- 也有利于各個部分的獨立測試芍秆。
注意:這三種模式主要看的是mediator同View之間的關(guān)系。mediator和Model的關(guān)系都是相同的翠勉。
MVC
MVC的流程大概如下:
- A View triggers an action to happen on the Controller
- The Controller executes said action and updates the Model
- The controller may receive a notification from the model that it was updated.
- The Controller updates the View
代碼:View通知Controller所有的用戶操作妖啥,之后Controller更新Model,Model一般會通過kvo來通知別的Controller來更新View上的展示对碌。
對于Controller來說:
- managing the view hierarchy of the view they own.
- respond to the view loading, appearing, disappearing, and so on .
- model logic(Models)
- business logic(Views)
其中V和VC之間的界限模糊荆虱,是緊緊綁在一起的,Controller要維護(hù)View的生命周期朽们,要接受View的事件怀读,要對View進(jìn)行定制。另一方面骑脱,Controller會更新Model也要響應(yīng)Model的通知愿吹。似乎看起來Controller會變的很重。但這不是最嚴(yán)重的問題惜姐,最嚴(yán)重的問題是這種情況下View和Controller都會變的很重犁跪,變的難以復(fù)用。
iOS architecture, where MVC stands for Massive View Controller.
- 厚重的View Controller
由于大量的代碼被放進(jìn)view controller歹袁,導(dǎo)致他們變的相當(dāng)臃腫坷衍。在iOS中有的view controller里綿延成千上萬行代碼的事并不是前所未見的。這些超重app的突出情況包括:厚重的View Controller很難維護(hù)(由于其龐大的規(guī)模)条舔;包含幾十個屬性枫耳,使他們的狀態(tài)難以管理;遵循許多協(xié)議(protocol)孟抗,導(dǎo)致協(xié)議的響應(yīng)代碼和controller的邏輯代碼混淆在一起迁杨。
厚重的view controller很難測試,不管是手動測試或是使用單元測試凄硼,因為有太多可能的狀態(tài)铅协。將代碼分解成更小的多個模塊通常是件好事。(Massive view controllers are difficult to test, either manually or with unit tests, because they have so many possible states. )- 遺失的網(wǎng)絡(luò)邏輯
蘋果使用的MVC的定義是這么說的:所有的對象都可以被歸類為一個model摊沉,一個view狐史,或是一個controller。就這些。那么把網(wǎng)絡(luò)代碼放哪里骏全?和一個API通信的代碼應(yīng)該放在哪兒苍柏?
你可能試著把它放在model對象里,但是也會很棘手姜贡,因為網(wǎng)絡(luò)調(diào)用應(yīng)該使用異步试吁,這樣如果一個網(wǎng)絡(luò)請求比持有它的model生命周期更長,事情將變的復(fù)雜楼咳。顯然也不應(yīng)該把網(wǎng)絡(luò)代碼放在view里潘悼,因此只剩下controller了。這同樣是個壞主意爬橡,因為這加劇了厚重View Controller的問題。
那么應(yīng)該放在那里呢棒动?顯然MVC的3大組件根本沒有適合放這些代碼的地方糙申。- 較差的可測試性
MVC的另一個大問題是,它不鼓勵開發(fā)人員編寫單元測試。由于view controller混合了視圖處理邏輯和業(yè)務(wù)邏輯船惨,分離這些成分的單元測試成了一個艱巨的任務(wù)柜裸。大多數(shù)人選擇忽略這個任務(wù),那就是不做任何測試粱锐。- 定義模糊的“Manage”
之前我提到了view controller可以管理試圖的層次結(jié)構(gòu)疙挺;view controller有一個“view”屬性,并且可以通過IBOutlet訪問視圖的任何子視圖怜浅。當(dāng)有很多outlet時這樣做不易于擴(kuò)展铐然,在某種意義上,最好不要使用子視圖控制器(child view controller)來幫助管理子視圖(subview)恶座。
要點在哪搀暑?驗證用戶輸入的業(yè)務(wù)邏輯應(yīng)歸入controller還是model呢?
在這里有多個模糊的標(biāo)準(zhǔn)跨琳,似乎沒有人能完全達(dá)成一致自点。貌似無論如何,view和對應(yīng)的controller都緊緊的耦合在一起脉让,總之桂敛,還是會把它們當(dāng)成一個組件來對待。
在MVC中,VC更像是一個整體溅潜。也叫做M-VC术唬。
- 耦合度 —— View 和 Model 是互相獨立的,但是 View 和 View Controller 耦合度很高滚澜。
- 可測試性 —— 只有 Model 可以脫離實際運行環(huán)境進(jìn)行單元測試碴开。(View 和 Controller 之間的通訊基本上是不能進(jìn)行單元測試的。) Breaking your code up into smaller, more bite-sized pieces is typically a very good thing. A recent storycomes to mind. So that’s it: Unit Test models and networking code. Use UAT to test the rest, if it’s worth your time. Maybe manual testing the views and controllers would fit better into your work flow, especially for smaller apps. Finally, document whatever Unit Testing and UAT does not test, so you can be sure to manually test it before you ship.
上述例子是否不大容易測試?雖然我們可以通過新建 GreetingModel 類并將 greeting 字符串的生成代碼放到該類中來實現(xiàn)該部分代碼的獨立測試潦牛,但如果不調(diào)用 viewDidLoad didTapButton 方法眶掌,我們很多對 GreetingViewController 中 view 的顯示邏輯(雖然上述例子沒多少顯示邏輯)進(jìn)行測試。這也意味著在項目單元測試中巴碗,我們需要加載所有的 view朴爬,這對于單元測試來說是很糟糕的。mix view manipulation logic with business logic, separating out those components for the sake of unit testing becomes a herculean task. - 易用性 —— 相對于其他架構(gòu)橡淆,代碼量最少召噩,而且大部分開發(fā)者對此架構(gòu)都很熟悉,易用性良好逸爵。
Cocoa MVC is the pattern of your choice if you are not ready to invest more time in your architecture, and you feel that something with higher maintenance cost is an overkill for your tiny pet project.
Cocoa MVC is the best architectural pattern in terms of the speed of the development.(快速開發(fā)會用)
對于MVC來說具滴,有一部分邏輯確實是屬于 controller 的,但是也有一部分邏輯是不應(yīng)該被放置在 controller 中的师倔。比如构韵,將 model 中的 NSDate 轉(zhuǎn)換成 view 可以展示的 NSString 等。在 MVVM 中趋艘,我們將這些邏輯統(tǒng)稱為展示邏輯疲恢。
MVP
在 Apple 的 MVC 中,View 和 Controller 是緊密耦合的瓷胧,但在 MVP 中显拳,Presenter 與 View/View Controller 完全解耦,Presenter中沒有任何與 View 布局相關(guān)的代碼搓萧,View 可以很方便地進(jìn)行移植杂数。即便這樣,Presenter 依舊肩負(fù)著對 View 的數(shù)據(jù)更新和動作捕捉瘸洛。
耦合度 —— 在此架構(gòu)中耍休, Presenter 和 Model 模塊的職責(zé)功能最分明,同時具備一個被盡量簡化的
可測試性 —— 具備非常好的可測試性货矮,我們可以通過 View 來測試大部分業(yè)務(wù)邏輯羊精。
易用性 —— 對于我們這個沒有實用性的簡單例子來說,MVP 架構(gòu)的代碼量幾乎是 MVC 的兩倍囚玫,但同時喧锦,MVP 在思路上更加清晰。
MVVM
從圖上看是比MVP簡單了抓督,更不用說MVC了燃少。MVVM是一種軟件架構(gòu)模式,是MVC\MVP的演進(jìn)铃在,促進(jìn)了UI代碼和業(yè)務(wù)邏輯的分離(個人不認(rèn)為MVVM是從MVP進(jìn)化而來)阵具,我只覺得這是在MVP之后出現(xiàn)的一種“更好的”UI模式解決方案碍遍,但是用MVP來與之對比比較容易說明問題。
MVVM將表示邏輯從Controller里移除放到一個新對象里阳液,即viewmodel怕敬。這樣可以減少ViewController的復(fù)雜性,并使得表示邏輯更易于測試帘皿。ViewModel大致上就是MVP的Presenter和MVC的Controller了东跪,而View和ViewModel間沒有了MVP的界面接口,而是直接交互鹰溜,用數(shù)據(jù)“綁定”的形式讓數(shù)據(jù)更新的事件不需要開發(fā)人員手動去編寫特殊用例虽填,而是自動地雙向同步。數(shù)據(jù)綁定你可以認(rèn)為是Observer模式或者是Publish/Subscribe模式曹动,原理都是為了用一種統(tǒng)一的集中的方式實現(xiàn)頻繁需要被實現(xiàn)的數(shù)據(jù)更新問題斋日。
比起MVP,MVVM不僅簡化了業(yè)務(wù)與界面的依賴關(guān)系墓陈,還優(yōu)化了數(shù)據(jù)頻繁更新的解決方案恶守,甚至可以說提供了一種有效的解決模式。
- model :與 MVC 中的 model 一致跛蛋,包括數(shù)據(jù)模型、訪問數(shù)據(jù)庫的操作和網(wǎng)絡(luò)請求等痊硕;
- view :由 MVC 中的 view 和 controller 組成赊级,負(fù)責(zé) UI 的展示,綁定 viewModel 中的屬性岔绸,觸發(fā) viewModel 中的命令
- viewModel :從 MVC 的 controller 中抽取出來的展示邏輯理逊,負(fù)責(zé)從 model 中獲取 view 所需的數(shù)據(jù),轉(zhuǎn)換成 view 可以展示的數(shù)據(jù)盒揉,并暴露公開的屬性和命令供 view 進(jìn)行綁定晋被;(只處理自己內(nèi)部的state,state的更改會自動同步到view中,也叫數(shù)據(jù)綁定)
- binder:在 MVVM 中刚盈,聲明式的數(shù)據(jù)和命令綁定是一個隱含的約定羡洛,它可以讓開發(fā)者非常方便地實現(xiàn) view 和 viewModel的同步,避免編寫大量繁雜的樣板化代碼藕漱。
(MVVM 還使用了與 Supervising version MVP 架構(gòu)類似的「綁定」機(jī)制欲侮;但是,這個「綁定」并非應(yīng)用于 View 和 Model 之間肋联,而是應(yīng)用于 View 和 View Model 之間偶房。在運行過程中济欢,View Model 監(jiān)聽著 Model 的變化,并根據(jù) Model 的變化來更新自身對應(yīng)的變量,同時,由于在 View 和 View Model 間設(shè)置了「綁定」放钦,View Model 的變化也會「觸發(fā)」 View 的更新。view model和model的關(guān)系,和上面圖中controller和model的關(guān)系是完全一樣的如孝。也就是說view model通過持有&更新model,通過響應(yīng)model的通知來更新邏輯舀奶。區(qū)別在于和view的關(guān)系暑竟,是綁定的關(guān)系(提供一種松耦合的關(guān)系,可以幫我們復(fù)用view和view model))
優(yōu)缺點:
- 耦合度 —— 雖然在我們的例子中并不明顯育勺,但實際上 MVVM 中的 View 比 MVP 中的 View 具備更多的職責(zé)但荤。在 MVVM 中 View 通過與 View Model 間的「綁定」來更新自身,而在 MVP 中涧至,View 只是傳遞事件給 Presenter 腹躁,View 并不更新自身。
- 可測試性 —— 鑒于View Model 對 View 一無所知南蓬,我們可以很容易地對 View Model 進(jìn)行單獨測試纺非。雖然 View 同樣可以做單元測試,但需要遍歷所有頁面赘方。
- 易用性 —— 與 MVP 架構(gòu)相比烧颖,MVVM 具備幾乎一樣的代碼量,但在實際項目中窄陡,若使用 MVP 架構(gòu)炕淮,你需要傳遞所有 View 上的事件到 Presenter,同時在 Presenter 中手動更新 View 跳夭,而 MVVM 則不需要這樣的操作涂圆,所以實際使用中 MVVM 會比 MVP 輕巧很多。
- 可以兼容當(dāng)下的MVC結(jié)構(gòu)
該圖是推薦的viewmodel的實現(xiàn)方式币叹,首先這是標(biāo)準(zhǔn)的mvvm的實現(xiàn)模式润歉。
這里view model分為兩部分,一部分是最基礎(chǔ)的view model部分颈抚,只包含和的view的相關(guān)的一些屬性踩衩,作為父類。所有的業(yè)務(wù)邏輯寫在子類中贩汉,顯然不同子類就可以實現(xiàn)不同業(yè)務(wù)線的業(yè)務(wù)邏輯九妈。
Under MVVM, the view and view controller become formally connected; we treat them as one. Views still don’t have references to the model, but neither do controllers. Instead, they reference the view model.
The one thing that does not belong in the view model is any reference to the view itself. The logic in the view model should be just as applicable on iOS as it is on OS X. (In other words, don’t #import UIKit.h in your view models and you’ll be fine.)
總結(jié)
在一個 app 中使用多種軟件架構(gòu)其實是很正常的。例如你的項目開始時使用的是 MVC 雾鬼,后面你可能發(fā)現(xiàn)個別復(fù)雜的頁面使用 MVC 架構(gòu)實現(xiàn)時會變得難以維護(hù)萌朱,此時你可能會使用 MVVM 架構(gòu)對該界面代碼進(jìn)行重構(gòu)。但并不需要修改其他使用 MVC 架構(gòu)的運行良好的頁面代碼策菜。
對于MV(X)家族晶疼,都是在經(jīng)典MVC基礎(chǔ)上隨著時代的發(fā)展酒贬、應(yīng)用環(huán)境的變化衍變出來的。實現(xiàn)MV(X)模式的這些框架到底歸屬于哪種模式翠霍,也不必泥古锭吨。MV(X)是一個很有爭議性的話題,能夠構(gòu)建一個健壯寒匙、具有良好設(shè)計零如、遵從關(guān)注點分離的項目比花時間去爭論到底是MV(X)更有意義。
如何綁定锄弱?
舉個例子:
Person:NSString *salutation
PersonViewController:一些判斷以及格式化的邏輯 self.namelabel.text = self.model.salutation
加入Viewmodel后 (同樣的代碼只是移動了位置考蕾,便簡化了VC,也方便了單元測試)
PersonViewModel: 一些判斷以及格式化的邏輯
PersonViewController:只需要 self.namelabel.text = self.viewmodel.salutation
這個例子中的model是不可變的会宪,所以只在初始化的時候指定我們的viewmodel的屬性肖卧。對于可變model,我門還需要一些綁定機(jī)制掸鹅,一旦viewmodel上的model發(fā)生改變塞帐,那么view的屬性也需要更新,model的改變應(yīng)該級聯(lián)向下通過viewmodel進(jìn)入view巍沙。
OSX : Cocoa
iOS :KVO (需要很多樣板代碼)葵姥,ReactiveCocoa。
( ? 基于 KVO 庫設(shè)計的 RZDataBinding 和 SwiftBond
- 基于函數(shù)式編程設(shè)計的庫句携,如ReactiveCocoa, RxSwift和PromiseKit
)
但是:MVVM并未強制我們使用ReactiveCocoa榔幸,MVVM是一個偉大的典范,它自身獨立务甥,只是再有一個良好綁定框架時做的更好牡辽。
In practice, using ReactiveCocoa is a great way to glue all the moving pieces together.
ReactiveCocoa(函數(shù)式響應(yīng)式編程框架)
在 iOS 開發(fā)中喳篇,系統(tǒng)并沒有提供類似的框架可以讓我們方便地實現(xiàn) binder 功能敞临。而GitHub開源的RAC為我們提供了一個不錯的選擇。
在 iOS 的 MVVM 實現(xiàn)中麸澜,我們可以使用 RAC 來在 view 和 viewModel 之間充當(dāng) binder 的角色挺尿,優(yōu)雅地實現(xiàn)兩者之間的同步。此外炊邦,我們還可以把 RAC 用在 model 層编矾,使用 Signal 來代表異步的數(shù)據(jù)獲取操作,比如讀取文件馁害、訪問數(shù)據(jù)庫和網(wǎng)絡(luò)請求等窄俏。說明,RAC 的后一個應(yīng)用場景是與 MVVM 無關(guān)的碘菜,也就是說凹蜈,我們同樣可以在 MVC 的 model 層這么用限寞。
架構(gòu)相關(guān):MVVM With ReactiveCocoa
Basic MVVM with ReactiveCocoa(重要)
iOS中MVC已死MVVM當(dāng)立?
Model-View-ViewModel for iOS(en)
MVVM 介紹
KVO實現(xiàn)MVVM
iOS 軟件架構(gòu) - MVC, MVP, MVVM 和 VIPER 「譯」 原文(en)
被誤解的MVC和被神化的MVVM
https://objccn.io/issue-15-5/
MVVM 與 FRP 編程實戰(zhàn)(有視頻)
書籍:《FunctionalReactiveProgrammingOniOS》
測試相關(guān):
如何對ViewModel進(jìn)行單元測試
XCTest Assertions 及其種類
iOS 單元測試
What's Worth Unit Testing in Objective-C?