iOS開發(fā)中 MVVM 設(shè)計模式的探究

前言

一直在做一線的業(yè)務(wù)開發(fā)工作侨核,每天接觸業(yè)務(wù)線似炎,時間久了就開始思考如何能優(yōu)化架構(gòu)犬缨、提高維護(hù)效率,于是就接觸了MVVM恒削。

MVVM的出現(xiàn)主要是為了解決在開發(fā)過程中Controller越來越龐大的問題池颈,變得難以維護(hù),所以MVVM把數(shù)據(jù)加工的任務(wù)從Controller中解放了出來钓丰,使得Controller只需要專注于具體業(yè)務(wù)的工作躯砰,ViewModel則去負(fù)責(zé)數(shù)據(jù)加工并通過各種通知機(jī)制讓View響應(yīng)ViewModel的改變。

還有一個讓人很容易忽略的問題携丁,大部分國內(nèi)外資料闡述MVVM的時候都是這樣排布的:Model - View - ViewModel ,
客觀上造成了MVVM不需要Controller的錯覺琢歇,可事實(shí)上是這樣的么?

分析使用邏輯

ViewModel部分

我最開始聽說MVVM的時候是一臉懵逼的梦鉴,因?yàn)槲以谶@幾個子母中間沒有看到Controller的影子李茫,我對于Controller的使用邏輯一無所知。但是直覺告訴我肥橙,Controller依然還是不可獲取的一部分魄宏,至少操作View的時候還是會需要。ViewModel這個單詞本身也讓我產(chǎn)生了困擾快骗,這個術(shù)語本身可能導(dǎo)致混亂娜庇,因?yàn)樗俏覀円呀?jīng)知道的兩個術(shù)語的混搭,不知道這個結(jié)構(gòu)更偏向于ViewModel哪一邊方篮。

當(dāng)我查閱資料的時候名秀,網(wǎng)上大部分的MVVM的表述都是View <-> ViewModel <-> Model,都是在說ViewModel支持?jǐn)?shù)據(jù)的雙向綁定藕溅,表述模糊匕得,我還是不能完全明白ViewModel的真正用處和存在意義。

比如像下面的說法:

它將model信息轉(zhuǎn)變?yōu)関iew需要的信息,同時還將命令從view傳遞到model汁掠。View與Model通過ViewModel實(shí)現(xiàn)雙向綁定略吨。

我的著眼點(diǎn)放在了上面的第一句話上,即“它將model信息轉(zhuǎn)變?yōu)関iew信息考阱,同時還將命令從view傳遞到model”翠忠。當(dāng)我進(jìn)一步去理解ViewModel的作用的時候,我發(fā)現(xiàn)ViewModel更應(yīng)該被稱呼作為“View與Model中間的觀察協(xié)調(diào)器”乞榨,它主要作用是拿到原始的數(shù)據(jù)秽之,根據(jù)具體業(yè)務(wù)邏輯需要進(jìn)行處理,之后將處理好的東西塞到View中去吃既,其職責(zé)之一是靜態(tài)模型考榨,表示View顯示自身所需的數(shù)據(jù),這使View具有更清晰定義的任務(wù)鹦倚,即呈現(xiàn)視圖模型提供的數(shù)據(jù)河质,總結(jié)為一句話就是與View直接對應(yīng)的Model,邏輯上是屬于Model層震叙。

Controller部分

那么Controller的作用呢掀鹅?

MVVM其實(shí)是是基于胖Model的架構(gòu)思路建立的,然后在胖Model中拆出兩部分:Model和ViewModel媒楼。
MVC -> 胖Model -> Model+ ViewModel這個演化過程淫半。

和上文分析一致,ViewModel本質(zhì)上算是Model層(作為與View顯示自己所直接對應(yīng)的Model)匣砖,所以View并不適合直接持有ViewModel,反之則可以昏滴。因?yàn)閂iewModel有可能并不是一定只服務(wù)于特定的一個View猴鲫,使用更加松散的綁定關(guān)系能夠降低ViewModel和View之間的耦合度。現(xiàn)在可以說谣殊,Controller唯一關(guān)注的是使用來自ViewModel的數(shù)據(jù)配置和管理各種View拂共,Controller的作用就是負(fù)責(zé)控制ViewViewModel的綁定和調(diào)用關(guān)系,Controller不需要了解Web服務(wù)調(diào)用姻几,Core Data宜狐,model對象等,這些都是可以丟給ViewModel去操作蛇捌。

以下2張圖或許有更直觀的感受:

圖片來源:http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/

我來解釋一下圖片指代的意思:

  1. MVVM中的View本質(zhì)上代指的是View和Controller兩部分結(jié)構(gòu)抚恒。
  2. 而MVVM中的ViewModel則是抽離了Controller的一些業(yè)務(wù)出來組成了ViewModel,同時Controller的工作量減小了很多络拌。
  3. 當(dāng)Controller抽離出來ViewModel之后俭驮,MVVM中View也可以區(qū)分為View和Controller兩部分之后,就成了第二張圖MVCVM的結(jié)構(gòu),Controller的代銷也就變小了混萝。

所以說遗遵,歸根結(jié)底MVVM更應(yīng)該被形容為View <-> C <-> ViewModel <-> Model,即MVCVM的設(shè)計模式逸嘀,這樣就很清晰的解釋了Controller的位置以及作用车要。

進(jìn)一步梳理

通過以上,我們可以邏輯上得出崭倘,Controller應(yīng)該是持有了ViewModel和View翼岁,并對二者進(jìn)行判斷和匹配操作。ViewModel有可能并不是一定只服務(wù)于特定的一個View绳姨,Controller當(dāng)中也會有很多的View登澜,所以,同一個Controller可能持有很多個ViewModel飘庄,以此來實(shí)現(xiàn)不同的業(yè)務(wù)邏輯控制多重的View脑蠕。

Controller唯一關(guān)注的是使用來自ViewModel的數(shù)據(jù)配置和管理各種View,并讓ViewModel知道何時發(fā)生需要更改上游數(shù)據(jù)的相關(guān)用戶輸入跪削。 Controller不需要知道網(wǎng)絡(luò)請求谴仙、數(shù)據(jù)庫操作以及Model等,這樣就讓Controller更集中的去處理具體的業(yè)務(wù)邏輯碾盐。

Free-Converter.com-205842hoe46esq6u55noqd-60352002.png

圖片來源:http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/

  • Controller:負(fù)責(zé)View和ViewModel之間的調(diào)配和綁定關(guān)系晃跺,執(zhí)行業(yè)務(wù)邏輯。
  • View:展示UI毫玖,接受Action掀虎。
  • ViewModel:網(wǎng)絡(luò)請求,數(shù)據(jù)加工付枫,數(shù)據(jù)持有烹玉。
  • Model:原始數(shù)據(jù)。

大致會形成以下調(diào)用關(guān)系:


圖片一

圖片二

代碼演示

廢話不多說阐滩,先上Demo:https://github.com/derekhuangxu/HXMVVMDemo

下面我會摘出部分重要代碼二打,給大家講解一下具體的邏輯

MarshalModel

MarshalModel.h
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger lessonCount;

Model相對來說就比較簡單了,用來儲存原始數(shù)據(jù)掂榔。


MarshalViewModel

MarshalViewModel.h

@interface MarshalViewModel : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) NSString *detailTextString;
@property (nonatomic, assign, readonly) UITableViewCellAccessoryType cellType;
- (instancetype)initWithModel:(MarshalModel *)model;
@end

MarshalViewModel.m

@interface MarshalViewModel ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *detailTextString;
@property (nonatomic, assign) UITableViewCellAccessoryType cellType;
@end

@implementation MarshalViewModel

//依賴注入
- (instancetype)initWithModel:(MarshalModel *)model ; {
    if (self = [super init]) {
        self.name = model.name;
        if (model.lessonCount > 35) {
            self.detailTextString = @"多于30門課程";
            self.cellType = UITableViewCellAccessoryDetailDisclosureButton;
        } else {
            self.detailTextString = [NSString stringWithFormat:@"課程數(shù)量:%ld", (long)model.lessonCount];
            self.cellType = UITableViewCellAccessoryNone;
        }
    }
    return self;
}
  • ViewModel的.h文件中继效,只放置了初始化的代碼,執(zhí)行Model數(shù)據(jù)的注入装获,另外提供了向View暴露的接口瑞信,這部分要注意了,要加上readonly的標(biāo)示符穴豫,當(dāng)然也可以用GET方法代替喧伞,這樣可以避免View修改ViewModel中的數(shù)據(jù),保證數(shù)據(jù)從一條線注入,處理之后潘鲫,從另一條線取出翁逞。
  • ViewModel的.m文件,可以說是對于原始數(shù)據(jù)的處理操作溉仑,這里基本上就是對于原始數(shù)據(jù)轉(zhuǎn)化為View需要數(shù)據(jù)的處理挖函。當(dāng)然,大家也可以根據(jù)自己的需要增加Cache或者數(shù)據(jù)庫等的操作浊竟,也可以增加網(wǎng)絡(luò)數(shù)據(jù)獲取的操作怨喘。

MarshalCell

MarshalCell.h
@interface MarshalCell : UITableViewCell
@property (nonatomic, strong) MarshalViewModel *viewModel;
@end

MarshalCell.m
- (void)setViewModel:(MarshalViewModel *)viewModel {
    _viewModel = viewModel;
    self.textLabel.text = viewModel.name;
    self.detailTextLabel.text = viewModel.detailTextString;
    self.accessoryType = viewModel.cellType;
}

我只把.m文件中的SET方法給大家展示出來了,我覺得就可以說明問題振定,ViewModel中提供了View需要的一切處理好的關(guān)鍵要素必怜,那么View就會變得非常扁平,只需要處理點(diǎn)擊這種的Action事件后频,并且傳遞給Controller即可梳庆,不需要關(guān)注原始數(shù)據(jù)轉(zhuǎn)換的具體業(yè)務(wù)邏輯。


HomeViewController

HomeViewController.m

@interface HomeViewController () 
@property (nonatomic, strong) NSMutableArray <MarshalViewModel *>*viewModels;
@end

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    MarshalCell *cell = [[MarshalCell alloc] initWithStyle: UITableViewCellStyleSubtitle
                           reuseIdentifier: kMarshalCellIdentifier];
    cell.viewModel = self.viewModels[indexPath.row];
    return cell;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.viewModels.count;
}

- (void)getData {
    [[MarshalService shareInstance]fetchDataCompletion:^(NSArray<MarshalModel *> * _Nonnull data) {
        self.viewModels = [self viewModelsWithDatas:[data mutableCopy]];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData];
        });
    } failure:^(NSError * _Nonnull error) {
        NSLog(@"Failed to fetch courses:%@", error);
    }];
}

大家可以看到卑惜,我用了數(shù)組來儲存的ViewModel膏执,原因是因?yàn)槊總€Cell所對應(yīng)的的數(shù)據(jù)不一致導(dǎo)致UI展示不一致,所以每個Cell要有一個自己的ViewModel來填充自己UI數(shù)據(jù)露久。這就進(jìn)一步印證了上面說的更米,ViewModel本質(zhì)仍然是Model層

疑問點(diǎn)(不定期更新)

1毫痕、為什么View沒有.h接口的可讀處理征峦,而ViewModel中要有?

在ViewModel中數(shù)據(jù)單向注入消请,之后會有數(shù)據(jù)根據(jù)業(yè)務(wù)邏輯處理眶痰,之后向View提供數(shù)據(jù)接口,為了保證數(shù)據(jù)不會被污染梯啤,所以增加了`readonly`的標(biāo)示符,而View中則不需要以上的邏輯存哲。

2因宇、當(dāng)點(diǎn)擊某個Cell的某部分之后,如何向后傳值祟偷?

1.Controller找到對應(yīng)的ViewModel察滑,ViewModel作為Mother ViewModel,替代Controller做一個臟活修肠,去生成相對應(yīng)的Child ViewModel贺辰。
2.ViewModel將Child ViewModel返回給Controller。
3.Controller利用Child ViewModel生成下一頁面的Controller,執(zhí)行跳轉(zhuǎn)操作饲化。

Refrence

另外

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末莽鸭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吃靠,更是在濱河造成了極大的恐慌硫眨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巢块,死亡現(xiàn)場離奇詭異礁阁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)族奢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門姥闭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人越走,你說我怎么就攤上這事棚品。” “怎么了弥姻?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵南片,是天一觀的道長。 經(jīng)常有香客問我庭敦,道長疼进,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任秧廉,我火速辦了婚禮伞广,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疼电。我一直安慰自己嚼锄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布蔽豺。 她就那樣靜靜地躺著区丑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪修陡。 梳的紋絲不亂的頭發(fā)上沧侥,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音魄鸦,去河邊找鬼宴杀。 笑死,一個胖子當(dāng)著我的面吹牛拾因,可吹牛的內(nèi)容都是我干的旺罢。 我是一名探鬼主播旷余,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扁达!你這毒婦竟也來了正卧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤罩驻,失蹤者是張志新(化名)和其女友劉穎穗酥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惠遏,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砾跃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了节吮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抽高。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖透绩,靈堂內(nèi)的尸體忽然破棺而出翘骂,到底是詐尸還是另有隱情,我是刑警寧澤帚豪,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布碳竟,位于F島的核電站,受9級特大地震影響狸臣,放射性物質(zhì)發(fā)生泄漏莹桅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一烛亦、第九天 我趴在偏房一處隱蔽的房頂上張望诈泼。 院中可真熱鬧,春花似錦煤禽、人聲如沸铐达。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓮孙。三九已至,卻和暖如春选脊,著一層夾襖步出監(jiān)牢的瞬間杭抠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工知牌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斤程。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓角寸,卻偏偏與公主長得像菩混,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扁藕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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