7.6 開發(fā)模式
MVC
iOS - MVC 架構(gòu)模式 - QianChia - 博客園
-
介紹
- Model和View永遠(yuǎn)不能相互通信,只能通過Controller傳遞。
- Controller可以直接與Model對(duì)話,Model通過Notification和KVO機(jī)制與Controller間接通信。
- Controller可以直接與View對(duì)話,通過outlet,直接操作View,outlet 直接對(duì)應(yīng)到View中的控件,View通過action向Controller報(bào)告事件的發(fā)生。Controller是View的直接數(shù)據(jù)源。Controller是View的代理,以同步 View與Controller。
2c8eac8a.png -
M:模型(Model)
- 模型對(duì)象封裝了應(yīng)用程序的數(shù)據(jù),并定義操控和處理該數(shù)據(jù)的邏輯和運(yùn)算。例如,模型對(duì)象可能是表示商品數(shù)據(jù) list。用戶在視圖層中所進(jìn)行的創(chuàng)建或修改數(shù)據(jù)的操作,通過控制器對(duì)象傳達(dá)出去,最終會(huì)創(chuàng)建或更新模型對(duì)象。模型對(duì)象更改時(shí)(例如通過網(wǎng)絡(luò)連接接收到新數(shù)據(jù)),它通知控制器對(duì)象,控制器對(duì)象更新相應(yīng)的視圖對(duì)象。
-
V:視圖(view)
視圖對(duì)象是應(yīng)用程序中用戶可以看見的對(duì)象。視圖對(duì)象知道如何將自己繪制出來,可能對(duì)用戶的操作作出響應(yīng)。視圖對(duì)象的主要目的就是顯示來自應(yīng)用程序模型對(duì)象的數(shù)據(jù),并使該數(shù)據(jù)可被編輯。盡管如此,在 MVC 應(yīng)用程序中,視圖對(duì)象通常與模型對(duì)象分離。
在iOS應(yīng)用程序開發(fā)中,所有的控件、窗口等都繼承自 UIView,對(duì)應(yīng) MVC 中的 V。UIView 及其子類主要負(fù)責(zé) UI 的實(shí)現(xiàn),而 UIView 所產(chǎn)生的事件都可以采用委托的方式,交給 UIViewController 實(shí)現(xiàn)。
-
C:控制器(controller)
在應(yīng)用程序的一個(gè)或多個(gè)視圖對(duì)象和一個(gè)或多個(gè)模型對(duì)象之間,控制器對(duì)象充當(dāng)媒介??刂破鲗?duì)象因此是同步管道程序,通過它,視圖對(duì)象了解模型對(duì)象的更改,反之亦然??刂破鲗?duì)象還可以為應(yīng)用程序執(zhí)行設(shè)置和協(xié)調(diào)任務(wù),并管理其他對(duì)象的生命周期。
控制器對(duì)象解釋在視圖對(duì)象中進(jìn)行的用戶操作,并將新的或更改過的數(shù)據(jù)傳達(dá)給模型對(duì)象。模型對(duì)象更改時(shí),一個(gè)控制器對(duì)象會(huì)將新的模型數(shù)據(jù)傳達(dá)給視圖對(duì)象,以便視圖對(duì)象可以顯示它。
對(duì)于不同的 UIView,有相應(yīng)的 UIViewController,對(duì)應(yīng) MVC 中的 C。例如在 iOS 上常用的 UITableView,它所對(duì)應(yīng)的 Controller 就是UITableViewController。
MVC 自身的不足
-
MVC 是一個(gè)用來組織代碼的權(quán)威范式,也是構(gòu)建 iOS App 的標(biāo)準(zhǔn)模式。Apple 甚至是這么說的。在 MVC 下,所有的對(duì)象被歸類為一個(gè) model,一個(gè) view,或一個(gè) controller。Model 持有數(shù)據(jù),View 顯示與用戶交互的界面,而 View Controller 調(diào)解 Model 和 View 之間的交互。然而,隨著模塊的迭代我們?cè)絹碓桨l(fā)現(xiàn) MVC 自身存在著很多不足。
-
1)MVC 在現(xiàn)實(shí)應(yīng)用中的不足:
- 在 MVC 模式中 view 將用戶交互通知給控制器。view 的控制器通過更新 Model 來反應(yīng)狀態(tài)的改變。Model(通常使用 Key-Value-Observation)通知控制器來更新他們負(fù)責(zé)的 view。大多數(shù) iOS 應(yīng)用程序的代碼使用這種方式來組織。
-
2)愈發(fā)笨重的 Controller:
在傳統(tǒng)的 app 中模型數(shù)據(jù)一般都很簡(jiǎn)單,不涉及到復(fù)雜的業(yè)務(wù)數(shù)據(jù)邏輯處理,客戶端開發(fā)受限于它自身運(yùn)行的的平臺(tái)終端,這一點(diǎn)注定使移動(dòng)端不像 PC 前端那樣能夠處理大量的復(fù)雜的業(yè)務(wù)場(chǎng)景。然而隨著移動(dòng)平臺(tái)的各種深入,我們不得不考慮這個(gè)問題。傳統(tǒng)的 Model 數(shù)據(jù)大多來源于網(wǎng)絡(luò)數(shù)據(jù),拿到網(wǎng)絡(luò)數(shù)據(jù)后客戶端要做的事情就是將數(shù)據(jù)直接按照順序畫在界面上。隨著業(yè)務(wù)的越來越來的深入,我們依賴的 service 服務(wù)可能在大多時(shí)間無法第一時(shí)間滿足客戶端需要的數(shù)據(jù)需求,移動(dòng)端愈發(fā)的要自行處理一部分邏輯計(jì)算操作。這個(gè)時(shí)間一慣的做法是在控制器中處理,最終導(dǎo)致了控制器成了垃圾箱,越來越不可維護(hù)。
控制器 Controller 是 app 的 “膠水代碼”,協(xié)調(diào)模型和視圖之間的所有交互??刂破髫?fù)責(zé)管理他們所擁有的視圖的視圖層次結(jié)構(gòu),還要響應(yīng)視圖的 loading、appearing、disappearing 等等,同時(shí)往往也會(huì)充滿我們不愿暴露的 Model 的模型邏輯以及不愿暴露給視圖的業(yè)務(wù)邏輯。這引出了第一個(gè)關(guān)于 MVC 的問題...
視圖 view 通常是 UIKit 控件(component,這里根據(jù)習(xí)慣譯為控件)或者編碼定義的 UIKit 控件的集合。進(jìn)入 .xib 或者 Storyboard 會(huì)發(fā)現(xiàn)一個(gè) app、Button、Label 都是由這些可視化的和可交互的控件組成。View 不應(yīng)該直接引用 Model,并且僅僅通過 IBAction 事件引用 controller。業(yè)務(wù)邏輯很明顯不歸入 view,視圖本身沒有任何業(yè)務(wù)。
厚重的 View Controller 由于大量的代碼被放進(jìn) viewcontroller,導(dǎo)致他們變的相當(dāng)臃腫。在 iOS 中有的 view controller 里綿延成千上萬行代碼的事并不是前所未見的。這些超重 app 的突出情況包括:厚重的 View Controller 很難維護(hù)(由于其龐大的規(guī)模);包含幾十個(gè)屬性,使他們的狀態(tài)難以管理;遵循許多協(xié)議(protocol),導(dǎo)致協(xié)議的響應(yīng)代碼和 controller 的邏輯代碼混淆在一起。
厚重的 view controller 很難測(cè)試,不管是手動(dòng)測(cè)試或是使用單元測(cè)試,因?yàn)橛刑嗫赡艿臓顟B(tài)。將代碼分解成更小的多個(gè)模塊通常是件好事。
-
3)太過于輕量級(jí)的 Model:
- 早期的 Model 層,其實(shí)就是如果數(shù)據(jù)有幾個(gè)屬性,就定義幾個(gè)屬性,ARC 普及以后我們?cè)?Model 層的實(shí)現(xiàn)文件中基本上看不到代碼(無需再手動(dòng)管理釋放變量,Model 既沒有復(fù)雜的業(yè)務(wù)處理,也沒有對(duì)象的構(gòu)造,基本上 .m 文件中的代碼普遍是空的);同時(shí)與控制器的代碼越來厚重形成強(qiáng)烈的反差,這一度讓人不禁對(duì)現(xiàn)有的開發(fā)設(shè)計(jì)構(gòu)思有所懷疑。
-
4)遺失的網(wǎng)絡(luò)邏輯:
蘋果使用的 MVC 的定義是這么說的:所有的對(duì)象都可以被歸類為一個(gè) Model,一個(gè) view,或是一個(gè)控制器。就這些,那么把網(wǎng)絡(luò)代碼放哪里?和一個(gè) API 通信的代碼應(yīng)該放在哪兒?
你可能試著把它放在 Model 對(duì)象里,但是也會(huì)很棘手,因?yàn)榫W(wǎng)絡(luò)調(diào)用應(yīng)該使用異步,這樣如果一個(gè)網(wǎng)絡(luò)請(qǐng)求比持有它的 Model 生命周期更長(zhǎng),事情將變的復(fù)雜。顯然也不應(yīng)該把網(wǎng)絡(luò)代碼放在 view 里,因此只剩下控制器了。這同樣是個(gè)壞主意,因?yàn)檫@加劇了厚重控制器的問題。那么應(yīng)該放在那里呢?顯然 MVC 的 3 大組件根本沒有適合放這些代碼的地方。
-
5)較差的可測(cè)試性
MVC 的另一個(gè)大問題是,它不鼓勵(lì)開發(fā)人員編寫單元測(cè)試。由于控制器混合了視圖處理邏輯和業(yè)務(wù)邏輯,分離這些成分的單元測(cè)試成了一個(gè)艱巨的任務(wù)。大多數(shù)人選擇忽略這個(gè)任務(wù),那就是不做任何測(cè)試。
上文提到了控制器可以管理視圖的層次結(jié)構(gòu);控制器有一個(gè) “view” 屬性,并且可以通過 IBOutlet 訪問視圖的任何子視圖。當(dāng)有很多 outlet 時(shí)這樣做不易于擴(kuò)展,在某種意義上,最好不要使用子視圖控制器(child view controller)來幫助管理子視圖。在這里有多個(gè)模糊的標(biāo)準(zhǔn),似乎沒有人能完全達(dá)成一致。貌似無論如何,view 和對(duì)應(yīng)的 controller 都緊緊的耦合在一起,總之,還是會(huì)把它們當(dāng)成一個(gè)組件來對(duì)待。Apple 提供的這個(gè)組件一度以來在某種程度誤導(dǎo)了大多初學(xué)者,初學(xué)者將所有的視圖全部拖到 xib 中,連接大量的 IBoutLet 輸出口屬性,都是一些列問題。
-
-
示例
- model
// BookModel.h @interface BookModel : NSObject // 根據(jù)需要使用的數(shù)據(jù)創(chuàng)建數(shù) Modal 數(shù)據(jù)模型屬性變量 @property(nonatomic, copy)NSString *title; @property(nonatomic, copy)NSString *detail; @property(nonatomic, copy)NSString *icon; @property(nonatomic, copy)NSString *price; + (instancetype)bookModelWithDict:(NSDictionary *)dict; @end // BookModel.m @implementation BookModel + (instancetype)bookModelWithDict:(NSDictionary *)dict { BookModel *book = [[self alloc] init]; [book setValuesForKeysWithDictionary:dict]; return book; } @end- view
// BookCell.h @class BookModel; @interface BookCell : UITableViewCell // 創(chuàng)建 Cell 視圖包含的內(nèi)容,Cell 使用 xib 創(chuàng)建 @property (weak, nonatomic) IBOutlet UIImageView *iconView; @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UILabel *detailLabel; @property (weak, nonatomic) IBOutlet UILabel *priceLabel; // 創(chuàng)建 Cell 視圖賦值方法 @property (nonatomic, strong) BookModel *bookModel; @end // BookCell.m // 包含數(shù)據(jù)模型頭文件 #import "BookModel.h" @implementation BookCell // 從 Model 數(shù)據(jù)模型中取出數(shù)據(jù)更新 View 的內(nèi)容 - (void)setBookModel:(BookModel *)bookModel { _iconView.image = [UIImage imageNamed:bookModel.icon]; _titleLabel.text = bookModel.title; _detailLabel.text = bookModel.detail; _priceLabel.text = bookModel.price; } @end- controller
// ViewController.m // Modal 模型處理 // 聲明數(shù)據(jù)源 @property (nonatomic, strong) NSArray *myDataArray; // 加載模型數(shù)據(jù) - (NSArray *)myDataArray { if (_myDataArray == nil) { NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"]]; NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:array.count]; [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // KVC BookModel *bookModel = [BookModel bookModelWithDict:obj]; // 使用 Modal 數(shù)據(jù)模型初始化數(shù)據(jù)源數(shù)組 [arrayM addObject:bookModel]; }]; _myDataArray = [arrayM copy]; } return _myDataArray; } // View 視圖處理 UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, self.view.bounds.size.height - 20)]; myTableView.delegate = self; myTableView.dataSource = self; [myTableView registerNib:[UINib nibWithNibName:@"BookCell" bundle:nil] forCellReuseIdentifier:@"BookCell"]; [self.view addSubview:myTableView]; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.myDataArray count]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 80; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ BookCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BookCell" forIndexPath:indexPath]; // 從 Modal 數(shù)據(jù)模型中取出數(shù)據(jù)更新 View 的內(nèi)容 cell.bookModel = self.myDataArray[indexPath.row]; return cell; }
MVVM
iOS 關(guān)于MVVM Without ReactiveCocoa設(shè)計(jì)模式的那些事 - 簡(jiǎn)書
-
介紹
- 一種可以很好地解決Massive View Controller問題的辦法就是將 Controller 中的展示邏輯抽取出來,放置到一個(gè)專門的地方,而這個(gè)地方就是 viewModel 。MVVM衍生于MVC,是對(duì) MVC 的一種演進(jìn),它促進(jìn)了 UI 代碼與業(yè)務(wù)邏輯的分離。它正式規(guī)范了視圖和控制器緊耦合的性質(zhì),并引入新的組件。他們之間的結(jié)構(gòu)關(guān)系如下
- 在MVVM 中,view 和 view controller正式聯(lián)系在一起,我們把它們視為一個(gè)組件
- view 和 view controller 都不能直接引用model,而是引用視圖模型(viewModel)
- viewModel 是一個(gè)放置用戶輸入驗(yàn)證邏輯,視圖顯示邏輯,發(fā)起網(wǎng)絡(luò)請(qǐng)求和其他代碼的地方
- 使用MVVM會(huì)輕微的增加代碼量,但總體上減少了代碼的復(fù)雜性
76051646.png -
M:模型(Model)
- 和MVC中的model保持一致,完全取決于你的"偏好設(shè)置"。你可能會(huì)為model封裝一些額外的操作數(shù)據(jù)的業(yè)務(wù)邏輯,雖然蘋果是推崇你這么干的,但是筆者認(rèn)為不妥,這樣很可能會(huì)導(dǎo)致一個(gè)胖Model的產(chǎn)生,而且胖Model相對(duì)比較難移植,胖Model隨著產(chǎn)品的迭代會(huì)更加的Fat,最終難以維護(hù),一胖毀所有。我更傾向于把它當(dāng)做一個(gè)容納表現(xiàn)數(shù)據(jù)-模型(data-model)對(duì)象信息的結(jié)構(gòu)體(瘦Model),并通過一個(gè)單獨(dú)的管理類來維護(hù)/創(chuàng)建/管理模型的統(tǒng)一邏輯,又或者可以通過使用Category來擴(kuò)充業(yè)務(wù)邏輯。MVVM是基于胖Model的架構(gòu)思路建立的,然后在胖Model中拆出兩部分:Model和ViewModel(PS:感覺是否有點(diǎn)道理)。
-
V:視圖(view)
- 由 MVC 中的view和 controller 組成,負(fù)責(zé) UI 的展示,綁定 viewModel中的屬性,觸發(fā) viewModel 中的命令以及呈現(xiàn)由viewModel提供的數(shù)據(jù)。
-
VM:視圖模型(viewModel)
- 千萬不要把它與傳統(tǒng)數(shù)據(jù)-模型結(jié)構(gòu)中模型混為一談。 它的職責(zé)之一就是作為一個(gè)表現(xiàn)視圖顯示自身所需數(shù)據(jù)的靜態(tài)模型;但它也有收集, 解釋和轉(zhuǎn)換那些數(shù)據(jù)的責(zé)任。它是從 MVC 的 controller 中抽取出來的展示邏輯,負(fù)責(zé)從 model中獲取 view 所需的數(shù)據(jù),轉(zhuǎn)換成 view可以展示的數(shù)據(jù),并暴露公開的屬性和命令供 view 進(jìn)行綁定
-
優(yōu)點(diǎn)
- 減輕了控制器的負(fù)擔(dān),可以在VM中寫網(wǎng)絡(luò)層的邏輯,用戶輸入驗(yàn)證邏輯,視圖顯示邏輯等
-
注意
- view 引用viewModel ,但反過來不行(即不要在viewModel中引入#import UIKit.h,任何視圖本身的引用都不應(yīng)該放在viewModel中)(PS:基本要求,必須滿足)
- View持有ViewModel的引用,反之沒有
-
ViewModel持有Model的引用,反之沒有
8fe4617c.png
MVP
在iOS 上實(shí)現(xiàn)MVP(附代碼) - 簡(jiǎn)書
- M:模型(Model)
- V:視圖(view)
- P:主持(Presenter)


