前言
一直覺得對于具體功能模塊的設(shè)計模式來說,沒有什么值得研究和討論的。覺得iOS提供的MVC模式已經(jīng)能夠很好的利用到產(chǎn)品的開發(fā)中去,對于大多數(shù)開發(fā)人員來說,最熟悉最得心應(yīng)手的設(shè)計模式莫過于此。
但是隨著業(yè)務(wù)需求的膨漲、頁面交互的體驗優(yōu)化、業(yè)務(wù)模式的逐漸豐富,我們一直習(xí)以為常的MVC模式中,某些Controller的UI交互代碼+業(yè)務(wù)邏輯代碼已經(jīng)達(dá)到上千行的規(guī)模。當(dāng)我們維護(hù)app、或進(jìn)行新功能的擴(kuò)充、或提供其他模塊的調(diào)用接口時,復(fù)雜混亂的controller邏輯讓我們頭疼不已。
于是我們開始將Controlller的業(yè)務(wù)邏輯代碼進(jìn)行拆分,拆分部分逐漸形成了viewModel層,于是在現(xiàn)下移動客戶端領(lǐng)域開始興起了MVVM模式(V-VM-M)。隨著FaceBook的“React Native“跨平臺開發(fā)框架,以及之前就出現(xiàn)的ReactiveCocoa響應(yīng)式編程模式的推出,MVVM設(shè)計模式在IOS移動端正逐漸被得心應(yīng)手的開始使用。
本文在在學(xué)習(xí)了解React Native和ReactiveCocoa的基礎(chǔ)上,對在IOS開發(fā)框架中如何采用MVVM模式進(jìn)行了一定的探索和實踐,并在實際的工程項目中如何利用MVC和MVVM做混合模式開發(fā)給出一定的建議,部分內(nèi)容可能偏理論一點,不妥之處,望指教更正。
關(guān)于ReactiveCocoa和MVVM的學(xué)習(xí)資料:點擊這里-Chrome書簽
通過學(xué)習(xí)資料了解完ReactiveCocoa的基本概念之后,我們可以從下面一張圖對ReactiveCocoa有一個整體理解:

核心概念是RACSignal,相當(dāng)于一個信號管道,接收信號源的信號,將信號依次發(fā)送給訂閱者,在RACSignal基礎(chǔ)上我們在MVVM模式下用得比較多的是RACCommand,信號到來開始執(zhí)行,每次執(zhí)行產(chǎn)生一個回調(diào)Signal。 具體的理解可以參考學(xué)習(xí)資料,講的很清楚。
目前最新版本的ReactiveCocoa開始支持Swift,要求最低發(fā)布版本為IOS8,所以我們可以使用支持IOS6的最高版本, 當(dāng)接入ReactiveCocoa之后,在性能上會慢1~2倍,但不影響app的直接體驗,另外在調(diào)試部分也需要程序員看懂信號的來源和出處,打斷點進(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ā)實踐,具體的應(yīng)用模式如下:

一、關(guān)于view層和viewModel層的Binding
對于某個業(yè)務(wù)功能(這里指某個controller),從代碼集來看,肯定是一堆系統(tǒng)原生view組件和自定義view組件在controller.view中的聚集。所有的葉子view組件節(jié)點都是以controller.view為根節(jié)點,作為其subview存在于controller中。所以view層其實可以看作是以controller.view為根節(jié)點的樹狀結(jié)構(gòu),負(fù)責(zé)view的創(chuàng)建、布局、和viewModel的binding、以及頁面的交互效果(包括動畫、跳轉(zhuǎn)、切換等),只涉及到頁面交互處理,不涉及業(yè)務(wù)邏輯的處理。
而對于viewModel來說,一般和controller是一一對應(yīng)的,view層可以強依賴viewModel層,而viewModel層一定不能引用view層的任何對象。 view層中涉及業(yè)務(wù)邏輯處理的任何交互均需要通過signal信號告知viewModel層或者直接調(diào)用viewModel的command命令執(zhí)行,同時view層也可以監(jiān)聽viewModel中定義的signal或者command執(zhí)行發(fā)出的signal完成頁面交互邏輯的處理。
具體處理過程
在controller初始化時,為該controller初始化一個對應(yīng)的viewModel對象。這個viewModel可以只是個殼,viewModel中的具體屬性對象可以通過前一個controller賦值初始化,也可以在當(dāng)前controller中發(fā)出某個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)題、某個業(yè)務(wù)組件的icon圖片名稱), 這些屬性沒有必要放入viewModel層,也就沒有必要進(jìn)行binding。
(2) 對于controller中跟viewModel中的對象息息相關(guān)的各個view對象(獲取textField的輸入值作為viewModel中某個operation的輸入,點擊某個按鈕執(zhí)行viewModel的某個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)常用到的可能會有UITextField,UITextView、UIControl、UIButton、UIActionSheet、UIAlertView這幾個,具體的使用方法在ReativeCoccoa的示例中可以查看。其封裝核心是監(jiān)控view組件的delegate方法或者selector方法執(zhí)行,每當(dāng)執(zhí)行時發(fā)送一個執(zhí)行信號;view層將該信號和viewModel層進(jìn)行綁定,自己也可以監(jiān)控這些信號完成某些交互行為。
- (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主要是針對subview是通過單獨文件封裝的view組件,如UITableView的自定義tableviewCell,某些在基礎(chǔ)view組件上進(jìn)行封裝的view組件等。如果是新寫這些view組件,可以考慮通過ReactiveCocoa的方式完成;如果是已經(jīng)成型的組件,則需要根據(jù)具體情況看是否改造成響應(yīng)式模式;
(1)如果組件中只綁定一兩個viewModel中非集合類型的屬性值,則直接通過開放view組件的屬性值,在controller中完成binding; 具體的binding方法和情景1類似。
(2)如果組件中有較多的基礎(chǔ)view組件,且都和viewModel相關(guān),則考慮為該view組件定義一個view對象,并作為view組件的屬性。controller將view組件的viewModel屬性和controller對應(yīng)的viewModel相應(yīng)屬性綁定,當(dāng)controller中對應(yīng)的viewModel發(fā)生變化,將會把變化傳遞給view 組件,在view組件中根據(jù)viewModel的屬性變化去改變具體的基礎(chǔ)view組件顯示值;
這個跟Reative Native的props屬性傳遞的思想類似:對于一些component,通過屬性傳入,在componnet中可以通過這些props去對基礎(chǔ)UI組件賦值,當(dāng)傳入屬性值改變之后,對應(yīng)component的相應(yīng)UI組件的顯示值也會發(fā)生變化。
在引入ReactiveCocoa之前,view組件和viewModel的對應(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:
對于比較復(fù)雜的系統(tǒng)view,如tableView、colletionView、pageView等,目前ReactiveCocoa中沒有提供category進(jìn)行綁定。
個人覺得對tableview整體進(jìn)行封裝沒有必要,一方面對于app來說,大多數(shù)復(fù)雜的界面都是在tableview的基礎(chǔ)上定制完成的,定制越多,封裝就越?jīng)]有意義;另外這些復(fù)雜的系統(tǒng)view組件一般提供了很好的擴(kuò)展性和自刷新機(jī)制(ReloadData),如tableview的section、header view、footer view、tableviewcell等,這些擴(kuò)展性就對應(yīng)了情景2種提到的第二種情況。
對于這種情景的處理方法:我們可以通過傳入一個viewModel對象給自定義的view或者cell,當(dāng)頁面刷新的時候,可以根據(jù)監(jiān)控到的傳入viewModel變化刷新界面;即使是非定制的view,則可以對view的屬性直接通過controller對應(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的時候,重新讀取一次
[cell setContent:[self.fhViewModel.hallModel arenaHallDailyContent]];
return cell;
}
- 情景4:
而某些container-controller(tabController和NavigationController除外),在兩個或者多個controller之間切換,controller.view中基本沒有交互,交互主要在navigationBar上。NavigationBar的交互主要是controller跳轉(zhuǎn)和切換,可能需要從Model中獲取一些數(shù)據(jù)進(jìn)行判定。
對于這種情景,我覺得沒有必要通過MVVM模式進(jìn)行處理,直接按照MVC模式處理即可,Model的獲取通過Model層提供的APIService獲?。▎卫涮D(zhuǎn)或者切換的controller中可以直接通過APIService獲取之前已經(jīng)初始化的Model對象;
tip
(1) 關(guān)于view的常量定義仍然屬于View層,沒有必要將其放進(jìn)viewModel中(viewModel還只是保存view需要跟Model打交道,對其進(jìn)行加工的部分);
(2) 在view層,對于無交互且無數(shù)據(jù)刷新需求的view組件不進(jìn)行綁定; 對于有交互的view組件,如果交互不會對viewModel進(jìn)行改變,也無需綁定;對于一些需要等待viewModel初始化完成才能進(jìn)行數(shù)據(jù)顯示的view組件,則可以考慮統(tǒng)一監(jiān)控viewModel,當(dāng)viewModel有更新的時候統(tǒng)一刷新各個view組件;
(3) 一個controller擁有一個viewModel, 比較復(fù)雜的view組件也可以擁有一個viewModel,但其viewModel的初始化在controller的viewModel中完成;
關(guān)于controller的簡化和跳轉(zhuǎn)
引入ReactiveCocoa有兩個目的:
第一個:是在controller中一些交互比較頻繁、狀態(tài)邏輯較復(fù)雜的情況,可以通過ReactiveCocoa的Signal機(jī)制監(jiān)聽view組件的狀態(tài)變化簡化狀態(tài)邏輯的判定。 比如說ReactiveCocoa的官方經(jīng)典案例:用戶名密碼登錄框;
第二個目的就是本文主要闡述的方向,通過引入ReactiveCocoa簡化controller, 將controller中的交互邏輯和業(yè)務(wù)邏輯分開,形成有效的MVVM開發(fā)模式。在將MVVM模式應(yīng)用的具體的業(yè)務(wù)組件工程中可能會引出下面一些問題:
1. controller之間的跳轉(zhuǎn)到底應(yīng)該放在view層還是viewModel層?
2. 什么代碼可以從controller中分離,如何分離?
Controller間的跳轉(zhuǎn)
在傳統(tǒng)MVC模式下的controller跳轉(zhuǎn),需要在當(dāng)前controller中import另外一個controller頭文件,然后傳入?yún)?shù)初始化controller,再調(diào)用navigation-push 或者 viewController-present, 相當(dāng)于controller跳轉(zhuǎn)是放到了view層。傳統(tǒng)的MVC模式給我們帶來的痛苦是無法對controller層進(jìn)行單元測試,并且controlle中業(yè)務(wù)邏輯和交互邏輯混亂不堪,所以我們才希望通過MVVC模式來簡化controller中的代碼;
當(dāng)我們將業(yè)務(wù)邏輯拆分到viewModel中,那么controller的跳轉(zhuǎn)是繼續(xù)留在view層還是將其拆分到viewModel層呢?我們來看一下分別放在兩個層的優(yōu)缺點:
如果繼續(xù)放在view層:MVVM模式號稱的去UI的測試也就無法去驗證跳轉(zhuǎn)邏輯,無法做完整性測試;并且每個controller的viewModel是孤立的,只能對其做單元測試;
如果直接拆分放到viewModel層,它就背離了我們之前的分層標(biāo)準(zhǔn),viewModel層絕對不能依賴view層的任何對象;如果我們通過viewModel層提供的service去做跳轉(zhuǎn),在MVC模式和MVVM模式混合開發(fā)的工程中,則稍顯復(fù)雜
所以本文覺得還是把controller的跳轉(zhuǎn)繼續(xù)放在view層;相當(dāng)于通過view層的navigator去完成跳轉(zhuǎn),其實這跟React Native的跳轉(zhuǎn)也是不謀而合的(在react Native中,每一個controller都被當(dāng)作是navigator下的一個scene,每個scene跳轉(zhuǎn)的時候都會保留對全局navigator的引用,直接在view層的代碼中調(diào)用navigator.push完成)。另外controller的跳轉(zhuǎn)對于整個app來說,也是屬于view交互的定性。
交互邏輯和業(yè)務(wù)邏輯如何分離?
前文提到MVVM模式的主要工作就是將controller中的業(yè)務(wù)邏輯拆分出來,那在一個controller中如何定義業(yè)務(wù)邏輯,具體哪些代碼需要拆分到viewModel中呢?前文也提到view層的主要工作是完成view的創(chuàng)建、布局、動畫,以及交互和跳轉(zhuǎn)處理。
view組件創(chuàng)建過程中,一些屬性值需要在創(chuàng)建時通過參數(shù)傳入,如果這些屬性值是通過常量(如標(biāo)題、按鈕文字、alert提示文字、按鈕的圖片名稱等),則不需要放到viewModel中;如果屬性值是需要從Model中獲取的數(shù)據(jù)(有些時候需要加工處理)進(jìn)行初始化,則需要將其通過viewModel提供,一開始可以先傳入一個空的viewModel,并建立view屬性和viewModel的binding,當(dāng)viewModel初始化完成或者更新的時候,通過signal更新view組件。另外在創(chuàng)建、布局view的時候可能需要一些model屬性的判定,這些判斷邏輯直接通過引用viewModel的數(shù)值即可。view層除開viewModel的綁定之外,只能讀取viewModel中的值去初始化界面。
而交互邏輯和業(yè)務(wù)邏輯的主要交叉點出現(xiàn)在controller的生命周期和view組件的action操作中:
比如說contorller的生命周期,當(dāng)controller初始化并push之后,會先跳轉(zhuǎn)到viewDidload中初始化subview,然后在viewWillAppear中完成布局,并向viewModel或者model層請求數(shù)據(jù)。 或者在在生命周期中需要監(jiān)聽某些事件通知、執(zhí)行某些定時器等,這些邏輯代碼就應(yīng)該放到viewModel中。
另外對于view組件的action操作,當(dāng)touch事件發(fā)生之后,既要完成view的顯示或者隱藏、或重新布局、或者更新數(shù)據(jù),又需要向viewModel層發(fā)起某些操作,這些向viewModel發(fā)起的操作邏輯也必須放到viewModel中;
當(dāng)我們把這些業(yè)務(wù)邏輯拆分出去之后,如何在某些交互或者某個生命周期狀態(tài)發(fā)生時自動調(diào)用對應(yīng)的業(yè)務(wù)邏輯呢?業(yè)務(wù)邏輯在viewModel中又是如何組織的呢?
業(yè)務(wù)邏輯在viewModel中通過RACCommand進(jìn)行管理,當(dāng)調(diào)起某個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í)行完成或者錯誤的時候,通過RACSubject通知調(diào)用者。
//初始化更新Signal,供view層監(jiān)聽
self.refreshResultSignal = [[RACSubject subject] setNameWithFormat:@"CPArenaFottHallVM-refreshResultSignal"];
/**
* 重新刷新hallModel(hallModel其實應(yīng)該是一個比較通用的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)聽。也可以對這些信號進(jìn)行組合、過濾、map、返回參數(shù)的封裝,這些都是ReactiveCocoa提供的方法;
監(jiān)聽controller生命周期狀態(tài)如下:
//當(dāng)view層disappear時,通知viewModel層開始計時
[[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動作
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)聽之后,其實是可以刪去這些delegate方法,但是編譯器報警告,所以保留原有的delegate方法,只是方法實現(xiàn)為空;真正的方法實現(xiàn)放到Signal監(jiān)聽之中;
viewModel層和Model層之間的交互
在Model層,ReactiveCocoa提供如下基礎(chǔ)對象signal改造的支持:NSArray、NSData、NSDictionary、NSEnumerator、NSFileHandle、NSIndexSet、NSInvocation
NSNotificationCenter、NSObject、NSOrderedSet、NSSet、NSString、NSURLConnection、NSUserDefaults. 如果工程中的Model層也是通過響應(yīng)式編程實現(xiàn),這些category可能對Model層通知viewModel層有很大的作用,目前還沒有對model層響應(yīng)式編程仔細(xì)研究(這不是必要的),這里說一下Model層在MVVM模式的主要工作模式。
APIService的發(fā)起路徑和過程
在MVC模式下,我們通常直接在controller中創(chuàng)建Model層提供的APIService去獲取數(shù)據(jù)。而在MVVM模式中,相應(yīng)APIService的請求會轉(zhuǎn)移到ViewModel層,而ViewModel的初始化是根據(jù)Model層的訪問完成的,其具體訪問路徑如下:
(1)當(dāng)點擊某個按鈕或者發(fā)起初始化signal,點擊動作或者創(chuàng)建會向viewModel發(fā)出一個signal執(zhí)行RACCommand(RACCommand,執(zhí)行 一次產(chǎn)生一個signal, 這個command在viewModel層定義和創(chuàng)建),當(dāng)前view層會定監(jiān)聽RACCommand的執(zhí)行,并根據(jù)返回的event實時更新view;
(2)viewModel接受這個signal:
如果signal是創(chuàng)建ViewModel,則判斷當(dāng)前viewModel是否初始化,如果沒有,則向Model層發(fā)起一個 initial operation;
如果signal改變了viewModel中的值,viewModel的變化會自動通知binding的view組件更新;此外如果viewModel的改變和Model層有關(guān)聯(lián),則向Model層發(fā)起一個update operation;
當(dāng)前viewModel向Model發(fā)起operation之后,當(dāng)前viewModel需要定制監(jiān)控發(fā)起的這個signal,并將后臺返回的signal信號進(jìn)行處理,或更新viewModel,或向view層監(jiān)控的signal發(fā)送各個operation的完成事件;
(3)Model層不僅僅是實體對象的定義,還包括APIService的實現(xiàn),APIService通過向后臺發(fā)送request請求,當(dāng)請求返回的時候能夠根據(jù)實體定義初始化實體對象,并在Model層持有(或進(jìn)一步持久化,保存為coredata或者db中);
- 當(dāng)Model層接受到viewModel(MVVC)或者controller(MVC)的 initial operation時,查看Model層持有的對象,如果存在直接返回,如果不存在,則通過APIService向后臺或者本地持久化發(fā)出請求,并將請求狀態(tài)和結(jié)果push給ViewModel層或者controller層;
- 當(dāng)Model層接收到update operation時,一般是先通過APIService發(fā)送update operation,根據(jù)operation的結(jié)果操作本地持久化對象(或者重新拉取一遍,或者直接在update operation中返回數(shù)據(jù)對原有持久對象進(jìn)行更換);
關(guān)于Model和網(wǎng)絡(luò)請求的生命周期
關(guān)于Model層什么時機(jī)進(jìn)行初始化,Model層持有對象的生命周期,以及網(wǎng)絡(luò)請求的生命周期?
- Model層的初始化,分為不同的場景,初始化的時機(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ò)請求的生命周期:
(1) 如果是單例的service,網(wǎng)絡(luò)請求的生命周期肯定小于單例,service持久化的Model對象也會一直存在;
(2) 如果是非單例service,service持久化的Model對象隨著service的dealloc而銷毀, 網(wǎng)絡(luò)請求的生命周期也會被service cancel掉;仍然小于service;