一). iOS架構(gòu)的分類
在iOS中架構(gòu)有很多,最常用的MVC,MVVM,MVP, 不常用的有VIPER
1.1)傳統(tǒng)的MVC的架構(gòu)是這樣的:
上面是傳統(tǒng)的MVC的架構(gòu)雖說耦合極端嚴(yán)重,這也是在項(xiàng)目中非常常見的一種架構(gòu)形式,很多公司在使用這個(gè)模式,三個(gè)實(shí)體間相互都有通信块请,而且是緊密耦合的达传。這很顯然會(huì)大大降低了三者的復(fù)用性屿笼,而這正是我們不愿意看到的憨攒。很容易造成幾千行上萬(wàn)行的控制器,蘋果希望的MVC的架構(gòu)實(shí)際上不應(yīng)該是上述的MVC的架構(gòu),希望的是這樣的效果:
1.2) 蘋果希望的MVC的架構(gòu)是這樣的:
由于Controller是一個(gè)介于View 和 Model之間的協(xié)調(diào)器孵运,所以View和Model之間沒有任何直接的聯(lián)系, 這也是相對(duì)于傳統(tǒng)的MVC的變化,減少了View和Model之間的通信,事秀。Controller是一個(gè)最小可重用單元灾梦,這對(duì)我們來說是一個(gè)好消息,因?yàn)槲覀兛傄乙粋€(gè)地方來寫邏輯復(fù)雜度較高的代碼槐雾,而這些代碼又不適合放在Model中夭委。
這樣還是不是不適合單元測(cè)試,同時(shí)控制器還是很臃腫因此有人說Massive View Controller
1.3) MVC的弊端
通過上圖,我們可以看到在純粹的MVC設(shè)計(jì)模式中募强,Controller不得不承擔(dān)大量的工作:
- 網(wǎng)絡(luò)API請(qǐng)求
- 數(shù)據(jù)讀寫
- 日志統(tǒng)計(jì)
- 數(shù)據(jù)的處理(JSON<=>Object株灸,數(shù)據(jù)計(jì)算)
- 對(duì)View進(jìn)行布局,動(dòng)畫
- 處理Controller之間的跳轉(zhuǎn)(push/modal/custom)
- 處理View層傳來的事件擎值,返回到Model層
- 監(jiān)聽Model層慌烧,反饋給View層
于是,大量的代碼堆積在Controller層中鸠儿,MVC最后成了Massive View Controller(重量級(jí)視圖控制器)屹蚊。
為了解決這種問題,我們通常會(huì)為Controller瘦身进每,也就是把Controller中代碼抽出到不同的類中汹粤,引入MVVM就是為Controller瘦身的一個(gè)很好的實(shí)踐。
二) MVVM架構(gòu)
Controller中我們不需要再做多余的判斷品追,那些表示邏輯我們已經(jīng)移植到了ViewModel中玄括,ViewController明顯輕量了很多。ViewModel承擔(dān)了部分控制器的業(yè)務(wù),因此可以比較好的減輕控制器的負(fù)擔(dān)
2.1) MVC和MVVM代碼比較的差異
比如我們有一個(gè)需求:一個(gè)頁(yè)面肉瓦,需要判斷用戶是否手動(dòng)設(shè)置了用戶名遭京。如果設(shè)置了,正常顯示用戶名泞莉;如果沒有設(shè)置哪雕,則顯示“簡(jiǎn)書0122”這種格式。(雖然這些本應(yīng)是服務(wù)器端判斷的)
我們看看MVC和MVVM兩種架構(gòu)都是怎么實(shí)現(xiàn)這個(gè)需求的
2.2) MVC 代碼實(shí)例
Model類:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) NSInteger userId;
@end
ViewController類:
#import "HomeViewController.h"
#import "User.h"
@interface HomeViewController ()
@property (nonatomic, strong) UILabel *lb_userName;
@property (nonatomic, strong) User *user;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (_user.userName.length > 0) {
_lb_userName.text = _user.userName;
} else {
_lb_userName.text = [NSString stringWithFormat:@"簡(jiǎn)書%ld", _user.userId];
}
}
這里我們需要將表示邏輯也放在ViewController中鲫趁。
MVVM:
Model類:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) NSInteger userId;
@end
ViewModel類:
聲明:
#import <Foundation/Foundation.h>
#import "User.h"
@interface UserViewModel : NSObject
@property (nonatomic, strong) User *user;
@property (nonatomic, copy) NSString *userName;
- (instancetype)initWithUser:(User *)user;
@end
實(shí)現(xiàn):
#import "UserViewModel.h"
@implementation UserViewModel
- (instancetype)initWithUser:(User *)user {
self = [super init];
if (!self) return nil;
_user = user;
if (user.userName.length > 0) {
_userName = user.userName;
} else {
_userName = [NSString stringWithFormat:@"簡(jiǎn)書%ld", _user.userId];
}
return self;
}
@end
Controller類:
#import "HomeViewController.h"
#import "UserViewModel.h"
@interface HomeViewController ()
@property (nonatomic, strong) UILabel *lb_userName;
@property (nonatomic, strong) UserViewModel *userViewModel;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
_lb_userName.text = _userViewModel.userName;
}
可見斯嚎,Controller中我們不需要再做多余的判斷,那些表示邏輯我們已經(jīng)移植到了ViewModel中,ViewController明顯輕量了很多堡僻。MVVM還有另一個(gè)問題糠惫。把業(yè)務(wù)邏輯放到ViewModel中,雖然能夠?yàn)閁IViewController減負(fù)钉疫,但是只是把問題轉(zhuǎn)移了硼讽,最終ViewModel還是會(huì)變成另一個(gè)Massive ViewModel。
三) VIPER架構(gòu)的介紹
上面的ViewModel處理了很多控制器的業(yè)務(wù),不是很適合做單元測(cè)試,還是會(huì)導(dǎo)致Massive ViewModel,為了解決 Massive ViewModel需要對(duì)職責(zé)進(jìn)行進(jìn)一步劃分,那就是VIPER
VIPER的全稱是View-Interactor-Presenter-Entity-Router,據(jù)筆者了解豆瓣和Uber的iOS技術(shù)團(tuán)隊(duì)已經(jīng)在使用VIPER架構(gòu)
相比之前的MVX架構(gòu)牲阁,VIPER多出了兩個(gè)東西:Interactor(交互器)和Router(路由)固阁。
3.1) VIPER各部分職責(zé)如下:
View
提供完整的視圖,負(fù)責(zé)視圖的組合城菊、布局备燃、更新
向Presenter提供更新視圖的接口
將View相關(guān)的事件發(fā)送給Presenter
Presenter
接收并處理來自View的事件
向Interactor請(qǐng)求調(diào)用業(yè)務(wù)邏輯
向Interactor提供View中的數(shù)據(jù)
接收并處理來自Interactor的數(shù)據(jù)回調(diào)事件
通知View進(jìn)行更新操作
通過Router跳轉(zhuǎn)到其他View
Router
提供View之間的跳轉(zhuǎn)功能,減少了模塊間的耦合
初始化VIPER的各個(gè)模塊
Interactor
維護(hù)主要的業(yè)務(wù)邏輯功能凌唬,向Presenter提供現(xiàn)有的業(yè)務(wù)用例
維護(hù)并齐、獲取、更新Entity
當(dāng)有業(yè)務(wù)相關(guān)的事件發(fā)生時(shí)法瑟,處理事件冀膝,并通知Presenter
Entity和Model一樣的數(shù)據(jù)模型
3.2) VIPER之間的通信方式(協(xié)議)
常規(guī)的通信方式是A 模塊需要調(diào)用B 模塊的 方法或者屬性,直接在B 模塊的中的方法暴露出頭文件 中,如下面的方法所示:
@interface CNTCountPresenter : NSObject
- (void)updateCount:(NSUInteger)count;
- (void)updateCountAModel:(AModel)count;
- (void)updateCountBModel:(BModel)count;
- (void)updateCountCModel:(CModel)count;
- (void)updateCountDModel:(DModel)count;
@end
上面的方式來實(shí)現(xiàn)協(xié)議的調(diào)用的時(shí)候,需要導(dǎo)入文件,這樣本來只需要- (void)updateCountAModel:(AModel)count;
這個(gè)方法,結(jié)果導(dǎo)入了BModel, CModel, DModel
這樣造成了不必要的耦合,這是常規(guī)的頭文件包含的方式,這樣在后期無法拆分代碼和重構(gòu) ,整個(gè)項(xiàng)目會(huì)造成一種 網(wǎng)狀的關(guān)系
上面的網(wǎng)狀項(xiàng)目在實(shí)際項(xiàng)目中很常見,解耦也比較困難,為了相對(duì)解耦或者為了未來更加方便解耦或者拆分使用一種協(xié)議的方式解耦,解耦方法如下:比如讓A 和B 之間實(shí)現(xiàn)解耦:
interator 傳遞事件給presenter需要如下三步:
- 聲明協(xié)議
@protocol CNTCountInteractorOutput <NSObject>
- (void)updateCount:(NSUInteger)count;
@end
- 遵守協(xié)議
@interface CNTCountPresenter : NSObject <CNTCountInteractorOutput>
// 以前需要在這里暴露 現(xiàn)在通過遵守協(xié)議的方式 - (void)updateCount:(NSUInteger)count;
@end
- 設(shè)置屬性保存, interactor和presenter建立關(guān)系
CNTCountInteractor* interactor = [[CNTCountInteractor alloc] init];
interactor.output = presenter;
-
- interactor 中調(diào)用協(xié)議方法
[self.output updateCount:self.count];
詳細(xì)的例子參考:協(xié)議之間通信的demo
3.3)協(xié)議方式解耦
上述的模塊之間的通信通過協(xié)議實(shí)現(xiàn),可以實(shí)現(xiàn)最大的限度的解耦,在直播間重構(gòu)中也被廣泛使用是一種比較好的解耦方式,無論是MVC 和MVVM,MVP 還是VIPER 及其各種 變種的MVVM,變種的VIPER 都需要解決模塊之間的通信,都可以借鑒協(xié)議的方式來進(jìn)行解耦,協(xié)議來實(shí)現(xiàn)解耦在網(wǎng)絡(luò)通信中使用比較廣泛,網(wǎng)絡(luò)七層通信協(xié)議,其實(shí)在iOS 模塊化之間也可以大力借鑒
四) VIPER和MVX的區(qū)別
VIPER把MVC中的Controller進(jìn)一步拆分成了Presenter唁奢、Router和Interactor霎挟。和MVP中負(fù)責(zé)業(yè)務(wù)邏輯的Presenter不同,VIPER的Presenter的主要工作是在View和Interactor之間傳遞事件麻掸,并管理一些View的展示邏輯酥夭,主要的業(yè)務(wù)邏輯實(shí)現(xiàn)代碼都放在了Interactor里。Interactor的設(shè)計(jì)里提出了"用例"的概念脊奋,也就是把每一個(gè)會(huì)出現(xiàn)的業(yè)務(wù)流程封裝好熬北,這樣可測(cè)試性會(huì)大大提高。而Router則進(jìn)一步解決了不同模塊之間的耦合诚隙。所以讶隐,VIPER和上面幾個(gè)MVX相比,多總結(jié)出了幾個(gè)需要維護(hù)的東西:
View事件管理
數(shù)據(jù)事件管理
事件和業(yè)務(wù)的轉(zhuǎn)化
總結(jié)每個(gè)業(yè)務(wù)用例
模塊內(nèi)分層隔離
模塊間通信
而這里面久又,還可以進(jìn)一步細(xì)分一些職責(zé)巫延。VIPER實(shí)際上已經(jīng)把Controller的概念淡化了,這拆分出來的幾個(gè)部分地消,都有很明確的單一職責(zé)炉峰,有些部分之間是完全隔絕的,在開發(fā)時(shí)就應(yīng)該清晰地區(qū)分它們各自的職責(zé)脉执,而不是將它們視為一個(gè)Controller疼阔。
TODO
vipER與大型控制器重構(gòu)之間的異同點(diǎn)
viper簡(jiǎn)介
https://github.com/iSame7/ViperCode 產(chǎn)生模塊
https://github.com/objcio/issue-13-viper/tree/master/VIPER%20TODO
VIPER 實(shí)戰(zhàn)
產(chǎn)生VIPER的模塊
參考文獻(xiàn)
iOS 架構(gòu)模式--解密 MVC,MVP,MVVM以及VIPER架構(gòu)
iOS MVVM架構(gòu)
iOS VIPER架構(gòu)實(shí)踐(一):從MVC到MVVM到VIPER
MVVM與Controller瘦身實(shí)踐
作者開發(fā)經(jīng)驗(yàn)總結(jié)的文章推薦,持續(xù)更新學(xué)習(xí)心得筆記
五星推薦 Runtime 10種用法(沒有比這更全的了)
五星推薦 成為iOS頂尖高手婆廊,你必須來這里(這里有最好的開源項(xiàng)目和文章)
五星推薦 iOS逆向Reveal查看任意app 的界面
五星推薦手把手教你使用python自動(dòng)打包上傳應(yīng)用分發(fā)
JSPatch (實(shí)時(shí)修復(fù)App Store bug)學(xué)習(xí)(一)
iOS 高級(jí)工程師是怎么進(jìn)階的(補(bǔ)充版20+點(diǎn))
擴(kuò)大按鈕(UIButton)點(diǎn)擊范圍(隨意方向擴(kuò)展哦)
最簡(jiǎn)單的免證書真機(jī)調(diào)試(原創(chuàng))
通過分析微信app,學(xué)學(xué)如何使用@2x,@3x圖片
TableView之MVVM與MVC之對(duì)比
使用MVVM減少控制器代碼實(shí)戰(zhàn)(減少56%)
ReactiveCocoa添加cocoapods 配置圖文教程及坑總結(jié)