iOS中的VIPER和MVC,MVVM 架構(gòu)之間的比較

一). iOS架構(gòu)的分類

在iOS中架構(gòu)有很多,最常用的MVC,MVVM,MVP, 不常用的有VIPER

1.1)傳統(tǒng)的MVC的架構(gòu)是這樣的:

傳統(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)是這樣的:

蘋果希望的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)

MVVM架構(gòu).png

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)


VIPER架構(gòu).png

相比之前的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)狀的結(jié)構(gòu).jpeg

上面的網(wǎng)狀項(xiàng)目在實(shí)際項(xiàng)目中很常見,解耦也比較困難,為了相對(duì)解耦或者為了未來更加方便解耦或者拆分使用一種協(xié)議的方式解耦,解耦方法如下:比如讓A 和B 之間實(shí)現(xiàn)解耦:
interator 傳遞事件給presenter需要如下三步:

    1. 聲明協(xié)議
@protocol CNTCountInteractorOutput <NSObject>
- (void)updateCount:(NSUInteger)count;
@end
    1. 遵守協(xié)議
@interface CNTCountPresenter : NSObject <CNTCountInteractorOutput>
//  以前需要在這里暴露 現(xiàn)在通過遵守協(xié)議的方式 - (void)updateCount:(NSUInteger)count;
@end
    1. 設(shè)置屬性保存, interactor和presenter建立關(guān)系
  CNTCountInteractor* interactor = [[CNTCountInteractor alloc] init];
   interactor.output = presenter;
    1. 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的模塊

覺得不錯(cuò)就點(diǎn)個(gè)??.gif

參考文獻(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é)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迅细,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子淘邻,更是在濱河造成了極大的恐慌疯攒,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件列荔,死亡現(xiàn)場(chǎng)離奇詭異敬尺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)贴浙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門砂吞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崎溃,你說我怎么就攤上這事蜻直。” “怎么了袁串?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵概而,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我囱修,道長(zhǎng)赎瑰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任破镰,我火速辦了婚禮餐曼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鲜漩。我一直安慰自己源譬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布孕似。 她就那樣靜靜地躺著踩娘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喉祭。 梳的紋絲不亂的頭發(fā)上养渴,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音臂拓,去河邊找鬼厚脉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛胶惰,可吹牛的內(nèi)容都是我干的傻工。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼中捆!你這毒婦竟也來了鸯匹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泄伪,失蹤者是張志新(化名)和其女友劉穎殴蓬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蟋滴,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡染厅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了津函。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肖粮。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尔苦,靈堂內(nèi)的尸體忽然破棺而出涩馆,到底是詐尸還是另有隱情,我是刑警寧澤允坚,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布魂那,位于F島的核電站,受9級(jí)特大地震影響稠项,放射性物質(zhì)發(fā)生泄漏涯雅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一皿渗、第九天 我趴在偏房一處隱蔽的房頂上張望斩芭。 院中可真熱鬧轻腺,春花似錦乐疆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至误算,卻和暖如春仰美,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背儿礼。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工咖杂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚊夫。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓诉字,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子壤圃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容