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)形式,很多公司在使用這個模式,三個實(shí)體間相互都有通信,而且是緊密耦合的。這很顯然會大大降低了三者的復(fù)用性,而這正是我們不愿意看到的。很容易造成幾千行上萬行的控制器,蘋果希望的MVC的架構(gòu)實(shí)際上不應(yīng)該是上述的MVC的架構(gòu),希望的是這樣的效果:

1.2) 蘋果希望的MVC的架構(gòu)是這樣的:

蘋果希望的MVC架構(gòu)

由于Controller是一個介于View 和 Model之間的協(xié)調(diào)器,所以View和Model之間沒有任何直接的聯(lián)系, 這也是相對于傳統(tǒng)的MVC的變化,減少了View和Model之間的通信,。Controller是一個最小可重用單元,這對我們來說是一個好消息,因?yàn)槲覀兛傄乙粋€地方來寫邏輯復(fù)雜度較高的代碼,而這些代碼又不適合放在Model中。
這樣還是不是不適合單元測試,同時控制器還是很臃腫因此有人說Massive View Controller

1.3) MVC的弊端

通過上圖,我們可以看到在純粹的MVC設(shè)計(jì)模式中,Controller不得不承擔(dān)大量的工作:

  • 網(wǎng)絡(luò)API請求
  • 數(shù)據(jù)讀寫
  • 日志統(tǒng)計(jì)
  • 數(shù)據(jù)的處理(JSON<=>Object,數(shù)據(jù)計(jì)算)
  • 對View進(jìn)行布局,動畫
  • 處理Controller之間的跳轉(zhuǎn)(push/modal/custom)
  • 處理View層傳來的事件,返回到Model層
  • 監(jiān)聽Model層,反饋給View層
    于是,大量的代碼堆積在Controller層中,MVC最后成了Massive View Controller(重量級視圖控制器)。
    為了解決這種問題,我們通常會為Controller瘦身,也就是把Controller中代碼抽出到不同的類中,引入MVVM就是為Controller瘦身的一個很好的實(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代碼比較的差異

比如我們有一個需求:一個頁面,需要判斷用戶是否手動設(shè)置了用戶名。如果設(shè)置了,正常顯示用戶名;如果沒有設(shè)置,則顯示“簡書0122”這種格式。(雖然這些本應(yīng)是服務(wù)器端判斷的)
我們看看MVC和MVVM兩種架構(gòu)都是怎么實(shí)現(xiàn)這個需求的

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:@"簡書%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:@"簡書%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還有另一個問題。把業(yè)務(wù)邏輯放到ViewModel中,雖然能夠?yàn)閁IViewController減負(fù),但是只是把問題轉(zhuǎn)移了,最終ViewModel還是會變成另一個Massive ViewModel。

三) VIPER架構(gòu)的介紹

上面的ViewModel處理了很多控制器的業(yè)務(wù),不是很適合做單元測試,還是會導(dǎo)致Massive ViewModel,為了解決 Massive ViewModel需要對職責(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多出了兩個東西:Interactor(交互器)和Router(路由)。

3.1) VIPER各部分職責(zé)如下:

View

提供完整的視圖,負(fù)責(zé)視圖的組合、布局、更新
向Presenter提供更新視圖的接口
將View相關(guān)的事件發(fā)送給Presenter

Presenter

接收并處理來自View的事件
向Interactor請求調(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的各個模塊

Interactor

維護(hù)主要的業(yè)務(wù)邏輯功能,向Presenter提供現(xiàn)有的業(yè)務(wù)用例
維護(hù)、獲取、更新Entity
當(dāng)有業(yè)務(wù)相關(guān)的事件發(fā)生時,處理事件,并通知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)用的時候,需要導(dǎo)入文件,這樣本來只需要- (void)updateCountAModel:(AModel)count; 這個方法,結(jié)果導(dǎo)入了BModel, CModel, DModel 這樣造成了不必要的耦合,這是常規(guī)的頭文件包含的方式,這樣在后期無法拆分代碼和重構(gòu) ,整個項(xiàng)目會造成一種 網(wǎng)狀的關(guān)系

網(wǎng)狀的結(jié)構(gòu).jpeg

上面的網(wǎng)狀項(xiàng)目在實(shí)際項(xiàng)目中很常見,解耦也比較困難,為了相對解耦或者為了未來更加方便解耦或者拆分使用一種協(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ì)里提出了"用例"的概念,也就是把每一個會出現(xiàn)的業(yè)務(wù)流程封裝好,這樣可測試性會大大提高。而Router則進(jìn)一步解決了不同模塊之間的耦合。所以,VIPER和上面幾個MVX相比,多總結(jié)出了幾個需要維護(hù)的東西:
View事件管理
數(shù)據(jù)事件管理
事件和業(yè)務(wù)的轉(zhuǎn)化
總結(jié)每個業(yè)務(wù)用例
模塊內(nèi)分層隔離
模塊間通信
而這里面,還可以進(jìn)一步細(xì)分一些職責(zé)。VIPER實(shí)際上已經(jīng)把Controller的概念淡化了,這拆分出來的幾個部分,都有很明確的單一職責(zé),有些部分之間是完全隔絕的,在開發(fā)時就應(yīng)該清晰地區(qū)分它們各自的職責(zé),而不是將它們視為一個Controller。

TODO

vipER與大型控制器重構(gòu)之間的異同點(diǎn)

viper簡介

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的模塊

覺得不錯就點(diǎn)個??.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自動打包上傳應(yīng)用分發(fā)
JSPatch (實(shí)時修復(fù)App Store bug)學(xué)習(xí)(一)
iOS 高級工程師是怎么進(jìn)階的(補(bǔ)充版20+點(diǎn))
擴(kuò)大按鈕(UIButton)點(diǎn)擊范圍(隨意方向擴(kuò)展哦)
最簡單的免證書真機(jī)調(diào)試(原創(chuàng))
通過分析微信app,學(xué)學(xué)如何使用@2x,@3x圖片
TableView之MVVM與MVC之對比
使用MVVM減少控制器代碼實(shí)戰(zhàn)(減少56%)
ReactiveCocoa添加cocoapods 配置圖文教程及坑總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容