前言
一直覺得對(duì)于具體功能模塊的設(shè)計(jì)模式來說统舀,沒有什么值得研究和討論的匆骗。覺得iOS提供的MVC模式已經(jīng)能夠很好的利用到產(chǎn)品的開發(fā)中去,對(duì)于大多數(shù)開發(fā)人員來說誉简,最熟悉最得心應(yīng)手的設(shè)計(jì)模式莫過于此碉就。
但是隨著業(yè)務(wù)需求的膨漲、頁面交互的體驗(yàn)優(yōu)化闷串、業(yè)務(wù)模式的逐漸豐富瓮钥,我們一直習(xí)以為常的MVC模式中,某些Controller的UI交互代碼+業(yè)務(wù)邏輯代碼已經(jīng)達(dá)到上千行的規(guī)模烹吵。當(dāng)我們維護(hù)app碉熄、或進(jìn)行新功能的擴(kuò)充、或提供其他模塊的調(diào)用接口時(shí)肋拔,復(fù)雜混亂的controller邏輯讓我們頭疼不已锈津。
于是我們開始將Controlller的業(yè)務(wù)邏輯代碼進(jìn)行拆分,拆分部分逐漸形成了viewModel層凉蜂,于是在現(xiàn)下移動(dòng)客戶端領(lǐng)域開始興起了MVVM模式(V-VM-M)琼梆。隨著FaceBook的“React Native“跨平臺(tái)開發(fā)框架,以及之前就出現(xiàn)的ReactiveCocoa響應(yīng)式編程模式的推出窿吩,MVVM設(shè)計(jì)模式在IOS移動(dòng)端正逐漸被得心應(yīng)手的開始使用茎杂。
本文在在學(xué)習(xí)了解React Native和ReactiveCocoa的基礎(chǔ)上,對(duì)在IOS開發(fā)框架中如何采用MVVM模式進(jìn)行了一定的探索和實(shí)踐纫雁,并在實(shí)際的工程項(xiàng)目中如何利用MVC和MVVM做混合模式開發(fā)給出一定的建議煌往,部分內(nèi)容可能偏理論一點(diǎn),不妥之處先较,望指教更正携冤。
關(guān)于ReactiveCocoa和MVVM的學(xué)習(xí)資料:點(diǎn)擊這里-Chrome書簽
通過學(xué)習(xí)資料了解完ReactiveCocoa的基本概念之后,我們可以從下面一張圖對(duì)ReactiveCocoa有一個(gè)整體理解:
核心概念是RACSignal闲勺,相當(dāng)于一個(gè)信號(hào)管道,接收信號(hào)源的信號(hào)扣猫,將信號(hào)依次發(fā)送給訂閱者菜循,在RACSignal基礎(chǔ)上我們?cè)贛VVM模式下用得比較多的是RACCommand,信號(hào)到來開始執(zhí)行申尤,每次執(zhí)行產(chǎn)生一個(gè)回調(diào)Signal癌幕。 具體的理解可以參考學(xué)習(xí)資料衙耕,講的很清楚。
目前最新版本的ReactiveCocoa開始支持Swift勺远,要求最低發(fā)布版本為IOS8橙喘,所以我們可以使用支持IOS6的最高版本, 當(dāng)接入ReactiveCocoa之后胶逢,在性能上會(huì)慢1~2倍厅瞎,但不影響app的直接體驗(yàn),另外在調(diào)試部分也需要程序員看懂信號(hào)的來源和出處初坠,打斷點(diǎn)進(jìn)行調(diào)試和簸。
platform :ios, '6.0'
inhibit_all_warnings!
workspace 'WTestReactiveCocoa'
xcodeproj 'TestReactiveCocoa'
target :TestReactiveCocoa do
pod 'ReactiveCocoa', '~> 2.5'
#設(shè)置pod target需要link的工程target
link_with 'TestReactiveCocoa'
end
本文主要介紹通過ReactiveCocoa進(jìn)行MVVM模式的開發(fā)實(shí)踐,具體的應(yīng)用模式如下:
一碟刺、關(guān)于view層和viewModel層的Binding
對(duì)于某個(gè)業(yè)務(wù)功能(這里指某個(gè)controller)锁保,從代碼集來看,肯定是一堆系統(tǒng)原生view組件和自定義view組件在controller.view中的聚集半沽。所有的葉子view組件節(jié)點(diǎn)都是以controller.view為根節(jié)點(diǎn)爽柒,作為其subview存在于controller中。所以view層其實(shí)可以看作是以controller.view為根節(jié)點(diǎn)的樹狀結(jié)構(gòu)者填,負(fù)責(zé)view的創(chuàng)建浩村、布局、和viewModel的binding幔托、以及頁面的交互效果(包括動(dòng)畫穴亏、跳轉(zhuǎn)、切換等)重挑,只涉及到頁面交互處理嗓化,不涉及業(yè)務(wù)邏輯的處理。
而對(duì)于viewModel來說谬哀,一般和controller是一一對(duì)應(yīng)的刺覆,view層可以強(qiáng)依賴viewModel層,而viewModel層一定不能引用view層的任何對(duì)象史煎。 view層中涉及業(yè)務(wù)邏輯處理的任何交互均需要通過signal信號(hào)告知viewModel層或者直接調(diào)用viewModel的command命令執(zhí)行谦屑,同時(shí)view層也可以監(jiān)聽viewModel中定義的signal或者command執(zhí)行發(fā)出的signal完成頁面交互邏輯的處理。
具體處理過程
在controller初始化時(shí)篇梭,為該controller初始化一個(gè)對(duì)應(yīng)的viewModel對(duì)象氢橙。這個(gè)viewModel可以只是個(gè)殼,viewModel中的具體屬性對(duì)象可以通過前一個(gè)controller賦值初始化恬偷,也可以在當(dāng)前controller中發(fā)出某個(gè)signal之后通過Model層提供的service進(jìn)行初始化悍手。
- (instancetype)init
{
if (self = [super init]) {
_fhcViewModel = [CPArenaFootHallContainerViewModel new];
[self setupContent];
}
return self;
}
完成初始化之后,需要在viewDidLoad方法中在view組件創(chuàng)建之后,完成view層和viewModel層的binding坦康。
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupContent];
[self bindViewModel];
}
/**
* 將controller中的view和ViewModel進(jìn)行binding
*/
-(void) bindViewModel{
//監(jiān)控初始化hallModel command的執(zhí)行
@weakify(self);
[[self.fhcViewModel.loadHallModel execute:@(YES)] subscribeNext:^(id statusCode){
//to do something
}];
}
tip:
(1) 并不是所有的view組件都需要和viewModel綁定竣付,通常如果view組件的顯示屬性是常量定義(如顯示標(biāo)題、某個(gè)業(yè)務(wù)組件的icon圖片名稱), 這些屬性沒有必要放入viewModel層滞欠,也就沒有必要進(jìn)行binding古胆。
(2) 對(duì)于controller中跟viewModel中的對(duì)象息息相關(guān)的各個(gè)view對(duì)象(獲取textField的輸入值作為viewModel中某個(gè)operation的輸入,點(diǎn)擊某個(gè)按鈕執(zhí)行viewModel的某個(gè)command), 則需要在controller中進(jìn)行綁定筛璧。
不同的情景其binding的方法也不一樣:
- 情景1:
如果subview是UIkit自帶的view組件(如label逸绎,button, textfield, textview, imageView, alert, action sheet等等),則可以直接通過ReactiveCocoa直接進(jìn)行view和viewModel之間的綁定隧哮。
ReactiveCocoa提供綁定category的的基本View組件包括:MKAnnotationView桶良、UIActionSheet、UIAlertView沮翔、UIBarButtonItem陨帆、UIButton、UICollectionReusableView采蚀、UIControl疲牵、UIDatePicker、UIGestureRecognizer榆鼠、UIImagePickerController纲爸、UIRefreshControl、UISegmentedControl妆够、UISlider识啦、UIStepper、UISwitch神妹、UITableViewCell颓哮、UITableViewHeaderFooterView、UITextField鸵荠、UITextView冕茅。
而我們平常經(jīng)常用到的可能會(huì)有UITextField,UITextView蛹找、UIControl姨伤、UIButton、UIActionSheet庸疾、UIAlertView這幾個(gè)乍楚,具體的使用方法在ReativeCoccoa的示例中可以查看。其封裝核心是監(jiān)控view組件的delegate方法或者selector方法執(zhí)行届慈,每當(dāng)執(zhí)行時(shí)發(fā)送一個(gè)執(zhí)行信號(hào)炊豪;view層將該信號(hào)和viewModel層進(jìn)行綁定凌箕,自己也可以監(jiān)控這些信號(hào)完成某些交互行為拧篮。
- (void)bindViewModel {
self.title = self.viewModel.title;
RAC(self.viewModel, searchText) = self.searchTextField.rac_textSignal;
self.searchButton.rac_command = self.viewModel.executeSearch;
RAC([UIApplication sharedApplication], networkActivityIndicatorVisible) = self.viewModel.executeSearch.executing;
RAC(self.loadingIndicator, hidden) = [self.viewModel.executeSearch.executing not];
[self.viewModel.executeSearch.executionSignals
subscribeNext:^(id x) {
[self.searchTextField resignFirstResponder];
}];
}
- 情景2:
情景2主要是針對(duì)subview是通過單獨(dú)文件封裝的view組件词渤,如UITableView的自定義tableviewCell,某些在基礎(chǔ)view組件上進(jìn)行封裝的view組件等串绩。如果是新寫這些view組件缺虐,可以考慮通過ReactiveCocoa的方式完成;如果是已經(jīng)成型的組件礁凡,則需要根據(jù)具體情況看是否改造成響應(yīng)式模式高氮;
(1)如果組件中只綁定一兩個(gè)viewModel中非集合類型的屬性值,則直接通過開放view組件的屬性值顷牌,在controller中完成binding剪芍; 具體的binding方法和情景1類似。
(2)如果組件中有較多的基礎(chǔ)view組件窟蓝,且都和viewModel相關(guān)罪裹,則考慮為該view組件定義一個(gè)view對(duì)象,并作為view組件的屬性运挫。controller將view組件的viewModel屬性和controller對(duì)應(yīng)的viewModel相應(yīng)屬性綁定状共,當(dāng)controller中對(duì)應(yīng)的viewModel發(fā)生變化,將會(huì)把變化傳遞給view 組件谁帕,在view組件中根據(jù)viewModel的屬性變化去改變具體的基礎(chǔ)view組件顯示值峡继;
這個(gè)跟Reative Native的props屬性傳遞的思想類似:對(duì)于一些component,通過屬性傳入匈挖,在componnet中可以通過這些props去對(duì)基礎(chǔ)UI組件賦值碾牌,當(dāng)傳入屬性值改變之后,對(duì)應(yīng)component的相應(yīng)UI組件的顯示值也會(huì)發(fā)生變化儡循。
在引入ReactiveCocoa之前舶吗,view組件和viewModel的對(duì)應(yīng)如下所示:
- (void)setContent:(CPArenaMatchViewModel *)content
{
if ([content.sps count] != 3 || [content.supportRates count] != 3) {
return;
}
[self.winButton setHeaderText:content.teamAName];
[self.winButton setDetailText:[NSString stringWithFormat:@"勝 %@",content.sps[0]]];
[self.drawButton setHeaderText:@"平"];
}
在引入ReactiveCocoa之后,具體viewModel和view組件的binding過程則根情景1種的binding類似贮折。
- 情景3:
對(duì)于比較復(fù)雜的系統(tǒng)view裤翩,如tableView、colletionView调榄、pageView等踊赠,目前ReactiveCocoa中沒有提供category進(jìn)行綁定。
個(gè)人覺得對(duì)tableview整體進(jìn)行封裝沒有必要每庆,一方面對(duì)于app來說筐带,大多數(shù)復(fù)雜的界面都是在tableview的基礎(chǔ)上定制完成的,定制越多缤灵,封裝就越?jīng)]有意義伦籍;另外這些復(fù)雜的系統(tǒng)view組件一般提供了很好的擴(kuò)展性和自刷新機(jī)制(ReloadData)蓝晒,如tableview的section、header view帖鸦、footer view芝薇、tableviewcell等,這些擴(kuò)展性就對(duì)應(yīng)了情景2種提到的第二種情況作儿。
對(duì)于這種情景的處理方法:我們可以通過傳入一個(gè)viewModel對(duì)象給自定義的view或者cell洛二,當(dāng)頁面刷新的時(shí)候,可以根據(jù)監(jiān)控到的傳入viewModel變化刷新界面攻锰;即使是非定制的view晾嘶,則可以對(duì)view的屬性直接通過controller對(duì)應(yīng)的viewModel直接賦值;
截取了tableView:cellForRowAtIndexPath的綁定代碼:
if ([moduleKey isEqualToString:kArenaHallDaily]) {
static NSString *dailyIdentifier = @"dailyCell";
CPArenaHallDailyCell *cell = [tableView dequeueReusableCellWithIdentifier:dailyIdentifier];
if (cell == nil) {
...
}
//直接讀取viewModel中的值進(jìn)行view組件的賦值娶吞,當(dāng)tableview reload的時(shí)候垒迂,重新讀取一次
[cell setContent:[self.fhViewModel.hallModel arenaHallDailyContent]];
return cell;
}
- 情景4:
而某些container-controller(tabController和NavigationController除外),在兩個(gè)或者多個(gè)controller之間切換妒蛇,controller.view中基本沒有交互机断,交互主要在navigationBar上。NavigationBar的交互主要是controller跳轉(zhuǎn)和切換材部,可能需要從Model中獲取一些數(shù)據(jù)進(jìn)行判定毫缆。
對(duì)于這種情景,我覺得沒有必要通過MVVM模式進(jìn)行處理乐导,直接按照MVC模式處理即可苦丁,Model的獲取通過Model層提供的APIService獲取(單例)物臂,其跳轉(zhuǎn)或者切換的controller中可以直接通過APIService獲取之前已經(jīng)初始化的Model對(duì)象旺拉;
tip
(1) 關(guān)于view的常量定義仍然屬于View層,沒有必要將其放進(jìn)viewModel中(viewModel還只是保存view需要跟Model打交道棵磷,對(duì)其進(jìn)行加工的部分)蛾狗;
(2) 在view層,對(duì)于無交互且無數(shù)據(jù)刷新需求的view組件不進(jìn)行綁定仪媒; 對(duì)于有交互的view組件沉桌,如果交互不會(huì)對(duì)viewModel進(jìn)行改變,也無需綁定算吩;對(duì)于一些需要等待viewModel初始化完成才能進(jìn)行數(shù)據(jù)顯示的view組件留凭,則可以考慮統(tǒng)一監(jiān)控viewModel,當(dāng)viewModel有更新的時(shí)候統(tǒng)一刷新各個(gè)view組件偎巢;
(3) 一個(gè)controller擁有一個(gè)viewModel蔼夜, 比較復(fù)雜的view組件也可以擁有一個(gè)viewModel,但其viewModel的初始化在controller的viewModel中完成压昼;
關(guān)于controller的簡化和跳轉(zhuǎn)
引入ReactiveCocoa有兩個(gè)目的:
第一個(gè):是在controller中一些交互比較頻繁求冷、狀態(tài)邏輯較復(fù)雜的情況瘤运,可以通過ReactiveCocoa的Signal機(jī)制監(jiān)聽view組件的狀態(tài)變化簡化狀態(tài)邏輯的判定。 比如說ReactiveCocoa的官方經(jīng)典案例:用戶名密碼登錄框匠题;
第二個(gè)目的就是本文主要闡述的方向拯坟,通過引入ReactiveCocoa簡化controller, 將controller中的交互邏輯和業(yè)務(wù)邏輯分開梧躺,形成有效的MVVM開發(fā)模式似谁。在將MVVM模式應(yīng)用的具體的業(yè)務(wù)組件工程中可能會(huì)引出下面一些問題:
1. controller之間的跳轉(zhuǎn)到底應(yīng)該放在view層還是viewModel層?
2. 什么代碼可以從controller中分離掠哥,如何分離?
Controller間的跳轉(zhuǎn)
在傳統(tǒng)MVC模式下的controller跳轉(zhuǎn)秃诵,需要在當(dāng)前controller中import另外一個(gè)controller頭文件续搀,然后傳入?yún)?shù)初始化controller,再調(diào)用navigation-push 或者 viewController-present菠净, 相當(dāng)于controller跳轉(zhuǎn)是放到了view層禁舷。傳統(tǒng)的MVC模式給我們帶來的痛苦是無法對(duì)controller層進(jìn)行單元測(cè)試,并且controlle中業(yè)務(wù)邏輯和交互邏輯混亂不堪毅往,所以我們才希望通過MVVC模式來簡化controller中的代碼牵咙;
當(dāng)我們將業(yè)務(wù)邏輯拆分到viewModel中,那么controller的跳轉(zhuǎn)是繼續(xù)留在view層還是將其拆分到viewModel層呢攀唯?我們來看一下分別放在兩個(gè)層的優(yōu)缺點(diǎn):
如果繼續(xù)放在view層:MVVM模式號(hào)稱的去UI的測(cè)試也就無法去驗(yàn)證跳轉(zhuǎn)邏輯洁桌,無法做完整性測(cè)試;并且每個(gè)controller的viewModel是孤立的侯嘀,只能對(duì)其做單元測(cè)試另凌;
如果直接拆分放到viewModel層,它就背離了我們之前的分層標(biāo)準(zhǔn)戒幔,viewModel層絕對(duì)不能依賴view層的任何對(duì)象吠谢;如果我們通過viewModel層提供的service去做跳轉(zhuǎn),在MVC模式和MVVM模式混合開發(fā)的工程中诗茎,則稍顯復(fù)雜
所以本文覺得還是把controller的跳轉(zhuǎn)繼續(xù)放在view層工坊;相當(dāng)于通過view層的navigator去完成跳轉(zhuǎn),其實(shí)這跟React Native的跳轉(zhuǎn)也是不謀而合的(在react Native中敢订,每一個(gè)controller都被當(dāng)作是navigator下的一個(gè)scene王污,每個(gè)scene跳轉(zhuǎn)的時(shí)候都會(huì)保留對(duì)全局navigator的引用,直接在view層的代碼中調(diào)用navigator.push完成)枢析。另外controller的跳轉(zhuǎn)對(duì)于整個(gè)app來說玉掸,也是屬于view交互的定性。
交互邏輯和業(yè)務(wù)邏輯如何分離醒叁?
前文提到MVVM模式的主要工作就是將controller中的業(yè)務(wù)邏輯拆分出來司浪,那在一個(gè)controller中如何定義業(yè)務(wù)邏輯泊业,具體哪些代碼需要拆分到viewModel中呢?前文也提到view層的主要工作是完成view的創(chuàng)建啊易、布局吁伺、動(dòng)畫,以及交互和跳轉(zhuǎn)處理租谈。
view組件創(chuàng)建過程中篮奄,一些屬性值需要在創(chuàng)建時(shí)通過參數(shù)傳入,如果這些屬性值是通過常量(如標(biāo)題割去、按鈕文字窟却、alert提示文字呻逆、按鈕的圖片名稱等)夸赫,則不需要放到viewModel中;如果屬性值是需要從Model中獲取的數(shù)據(jù)(有些時(shí)候需要加工處理)進(jìn)行初始化咖城,則需要將其通過viewModel提供茬腿,一開始可以先傳入一個(gè)空的viewModel,并建立view屬性和viewModel的binding宜雀,當(dāng)viewModel初始化完成或者更新的時(shí)候切平,通過signal更新view組件。另外在創(chuàng)建辐董、布局view的時(shí)候可能需要一些model屬性的判定悴品,這些判斷邏輯直接通過引用viewModel的數(shù)值即可。view層除開viewModel的綁定之外郎哭,只能讀取viewModel中的值去初始化界面他匪。
而交互邏輯和業(yè)務(wù)邏輯的主要交叉點(diǎn)出現(xiàn)在controller的生命周期和view組件的action操作中:
比如說contorller的生命周期,當(dāng)controller初始化并push之后夸研,會(huì)先跳轉(zhuǎn)到viewDidload中初始化subview邦蜜,然后在viewWillAppear中完成布局,并向viewModel或者model層請(qǐng)求數(shù)據(jù)亥至。 或者在在生命周期中需要監(jiān)聽某些事件通知悼沈、執(zhí)行某些定時(shí)器等,這些邏輯代碼就應(yīng)該放到viewModel中姐扮。
另外對(duì)于view組件的action操作絮供,當(dāng)touch事件發(fā)生之后,既要完成view的顯示或者隱藏茶敏、或重新布局壤靶、或者更新數(shù)據(jù),又需要向viewModel層發(fā)起某些操作惊搏,這些向viewModel發(fā)起的操作邏輯也必須放到viewModel中贮乳;
當(dāng)我們把這些業(yè)務(wù)邏輯拆分出去之后忧换,如何在某些交互或者某個(gè)生命周期狀態(tài)發(fā)生時(shí)自動(dòng)調(diào)用對(duì)應(yīng)的業(yè)務(wù)邏輯呢?業(yè)務(wù)邏輯在viewModel中又是如何組織的呢向拆?
業(yè)務(wù)邏輯在viewModel中通過RACCommand進(jìn)行管理亚茬,當(dāng)調(diào)起某個(gè)command的執(zhí)行之后,首先完成業(yè)務(wù)處理浓恳,并通過signal(return value)通知調(diào)用者發(fā)起回調(diào)刹缝。
//執(zhí)行添加leaveTimer
@weakify(self);
self.addLeaveTimerCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
@strongify(self);
[self addLeaveTimer];
return [RACSignal return:@(YES)];
}];
如果業(yè)務(wù)處理是異步的,則需要在viewModel中定義全局Signal(RACSubject)颈将,當(dāng)異步業(yè)務(wù)處理執(zhí)行完成或者錯(cuò)誤的時(shí)候梢夯,通過RACSubject通知調(diào)用者。
//初始化更新Signal吆鹤,供view層監(jiān)聽
self.refreshResultSignal = [[RACSubject subject] setNameWithFormat:@"CPArenaFottHallVM-refreshResultSignal"];
/**
* 重新刷新hallModel(hallModel其實(shí)應(yīng)該是一個(gè)比較通用的ViewModel)
*/
-(void)refreshContent
{
void (^ completion)(void) = ^{
[self.refreshResultSignal sendCompleted];
};
//通過service重新load HallModel數(shù)據(jù)
if(1){
[self.refreshResultSignal sendNext:@"hello"];
completion();
}
}
生命周期狀態(tài)的監(jiān)聽我們通過rac_signalforSelector去完成厨疙,某些view組件的action,則直接通過view組件的category疑务,或者監(jiān)聽view組件的delegate回調(diào)來完成監(jiān)聽。也可以對(duì)這些信號(hào)進(jìn)行組合梗醇、過濾知允、map、返回參數(shù)的封裝叙谨,這些都是ReactiveCocoa提供的方法温鸽;
監(jiān)聽controller生命周期狀態(tài)如下:
//當(dāng)view層disappear時(shí),通知viewModel層開始計(jì)時(shí)
[[self rac_signalForSelector:@selector(viewWillDisappear:)] subscribeNext:^(id x) {
@strongify(self);
[self.fhViewModel.addLeaveTimerCommand execute:nil];
}];
//監(jiān)控view層的顯示Signal
[[self rac_signalForSelector:@selector(viewWillAppear:)] subscribeNext:^(id x) {
@strongify(self);
[self.fhViewModel.refreshCheckCommand execute:nil];
}];
監(jiān)聽view組件的delegate回調(diào)如下:
//監(jiān)聽當(dāng)前HeadView的Accessory的click動(dòng)作
RACSignal *clickAccessorySignal = [self rac_signalForSelector:@selector(didClickAccessoryViewInHeaderView:) fromProtocol:@protocol(CPArenaHeaderViewDelegate)];
[[clickAccessorySignal map:^id(RACTuple *tuple) {
return tuple.first;
}] subscribeNext:^(CPArenaHeaderView *headerView) {
//to do something
}
tip:
- 將某些delegate方法通過signal監(jiān)聽之后手负,其實(shí)是可以刪去這些delegate方法涤垫,但是編譯器報(bào)警告,所以保留原有的delegate方法竟终,只是方法實(shí)現(xiàn)為空蝠猬;真正的方法實(shí)現(xiàn)放到Signal監(jiān)聽之中;
viewModel層和Model層之間的交互
在Model層,ReactiveCocoa提供如下基礎(chǔ)對(duì)象signal改造的支持:NSArray统捶、NSData榆芦、NSDictionary、NSEnumerator喘鸟、NSFileHandle匆绣、NSIndexSet、NSInvocation
NSNotificationCenter什黑、NSObject崎淳、NSOrderedSet、NSSet愕把、NSString拣凹、NSURLConnection森爽、NSUserDefaults. 如果工程中的Model層也是通過響應(yīng)式編程實(shí)現(xiàn),這些category可能對(duì)Model層通知viewModel層有很大的作用咐鹤,目前還沒有對(duì)model層響應(yīng)式編程仔細(xì)研究(這不是必要的)拗秘,這里說一下Model層在MVVM模式的主要工作模式。
APIService的發(fā)起路徑和過程
在MVC模式下祈惶,我們通常直接在controller中創(chuàng)建Model層提供的APIService去獲取數(shù)據(jù)雕旨。而在MVVM模式中,相應(yīng)APIService的請(qǐng)求會(huì)轉(zhuǎn)移到ViewModel層捧请,而ViewModel的初始化是根據(jù)Model層的訪問完成的凡涩,其具體訪問路徑如下:
(1)當(dāng)點(diǎn)擊某個(gè)按鈕或者發(fā)起初始化signal,點(diǎn)擊動(dòng)作或者創(chuàng)建會(huì)向viewModel發(fā)出一個(gè)signal執(zhí)行RACCommand(RACCommand疹蛉,執(zhí)行 一次產(chǎn)生一個(gè)signal活箕, 這個(gè)command在viewModel層定義和創(chuàng)建),當(dāng)前view層會(huì)定監(jiān)聽RACCommand的執(zhí)行可款,并根據(jù)返回的event實(shí)時(shí)更新view育韩;
(2)viewModel接受這個(gè)signal:
如果signal是創(chuàng)建ViewModel,則判斷當(dāng)前viewModel是否初始化闺鲸,如果沒有筋讨,則向Model層發(fā)起一個(gè) initial operation;
如果signal改變了viewModel中的值摸恍,viewModel的變化會(huì)自動(dòng)通知binding的view組件更新悉罕;此外如果viewModel的改變和Model層有關(guān)聯(lián),則向Model層發(fā)起一個(gè)update operation立镶;
當(dāng)前viewModel向Model發(fā)起operation之后壁袄,當(dāng)前viewModel需要定制監(jiān)控發(fā)起的這個(gè)signal,并將后臺(tái)返回的signal信號(hào)進(jìn)行處理媚媒,或更新viewModel嗜逻,或向view層監(jiān)控的signal發(fā)送各個(gè)operation的完成事件;
(3)Model層不僅僅是實(shí)體對(duì)象的定義欣范,還包括APIService的實(shí)現(xiàn)变泄,APIService通過向后臺(tái)發(fā)送request請(qǐng)求,當(dāng)請(qǐng)求返回的時(shí)候能夠根據(jù)實(shí)體定義初始化實(shí)體對(duì)象恼琼,并在Model層持有(或進(jìn)一步持久化妨蛹,保存為coredata或者db中);
- 當(dāng)Model層接受到viewModel(MVVC)或者controller(MVC)的 initial operation時(shí)晴竞,查看Model層持有的對(duì)象蛙卤,如果存在直接返回,如果不存在,則通過APIService向后臺(tái)或者本地持久化發(fā)出請(qǐng)求颤难,并將請(qǐng)求狀態(tài)和結(jié)果push給ViewModel層或者controller層神年;
- 當(dāng)Model層接收到update operation時(shí),一般是先通過APIService發(fā)送update operation行嗤,根據(jù)operation的結(jié)果操作本地持久化對(duì)象(或者重新拉取一遍已日,或者直接在update operation中返回?cái)?shù)據(jù)對(duì)原有持久對(duì)象進(jìn)行更換);
關(guān)于Model和網(wǎng)絡(luò)請(qǐng)求的生命周期
關(guān)于Model層什么時(shí)機(jī)進(jìn)行初始化栅屏,Model層持有對(duì)象的生命周期飘千,以及網(wǎng)絡(luò)請(qǐng)求的生命周期?
- Model層的初始化栈雳,分為不同的場(chǎng)景护奈,初始化的時(shí)機(jī)也不一樣:
(1) 如果Model是全局需要的,如UserModel哥纫,則可以在appdelegate中直接初始化霉旗;
(2) 如果是MVC模式,則直接在controller中直接調(diào)用APIService初始化蛀骇;
(3) 如果是MVVM模式厌秒,則通過調(diào)用viewModel中進(jìn)行初始化;
- 生命周期:
(1) 如果是全局Model擅憔,則通過單例Service進(jìn)行保持简僧;
(2) 如果是跟controller保持一致的,則通過普通service進(jìn)行初始化雕欺;
- 網(wǎng)絡(luò)請(qǐng)求的生命周期:
(1) 如果是單例的service,網(wǎng)絡(luò)請(qǐng)求的生命周期肯定小于單例棉姐,service持久化的Model對(duì)象也會(huì)一直存在屠列;
(2) 如果是非單例service,service持久化的Model對(duì)象隨著service的dealloc而銷毀伞矩, 網(wǎng)絡(luò)請(qǐng)求的生命周期也會(huì)被service cancel掉笛洛;仍然小于service;