基于ReactiveCocoa的MVVM探索

前言

一直覺得對(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è)整體理解:

FRP_ReactiveCocoa_large.png

核心概念是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)用模式如下:


MVVMReactiveCocoa.png

一碟刺、關(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):

  1. 如果繼續(xù)放在view層:MVVM模式號(hào)稱的去UI的測(cè)試也就無法去驗(yàn)證跳轉(zhuǎn)邏輯洁桌,無法做完整性測(cè)試;并且每個(gè)controller的viewModel是孤立的侯嘀,只能對(duì)其做單元測(cè)試另凌;

  2. 如果直接拆分放到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操作中:

  1. 比如說contorller的生命周期,當(dāng)controller初始化并push之后夸研,會(huì)先跳轉(zhuǎn)到viewDidload中初始化subview邦蜜,然后在viewWillAppear中完成布局,并向viewModel或者model層請(qǐng)求數(shù)據(jù)亥至。 或者在在生命周期中需要監(jiān)聽某些事件通知悼沈、執(zhí)行某些定時(shí)器等,這些邏輯代碼就應(yīng)該放到viewModel中姐扮。

  2. 另外對(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:

  1. 將某些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;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乃坤,一起剝皮案震驚了整個(gè)濱河市苛让,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湿诊,老刑警劉巖狱杰,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異厅须,居然都是意外死亡仿畸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來错沽,“玉大人簿晓,你說我怎么就攤上這事∏О#” “怎么了憔儿?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長放可。 經(jīng)常有香客問我谒臼,道長,這世上最難降的妖魔是什么吴侦? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任屋休,我火速辦了婚禮,結(jié)果婚禮上备韧,老公的妹妹穿的比我還像新娘劫樟。我一直安慰自己,他們只是感情好织堂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布叠艳。 她就那樣靜靜地躺著,像睡著了一般易阳。 火紅的嫁衣襯著肌膚如雪附较。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天潦俺,我揣著相機(jī)與錄音拒课,去河邊找鬼。 笑死事示,一個(gè)胖子當(dāng)著我的面吹牛早像,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肖爵,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卢鹦,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了劝堪?” 一聲冷哼從身側(cè)響起冀自,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秒啦,沒想到半個(gè)月后熬粗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帝蒿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年荐糜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暴氏,死狀恐怖延塑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情答渔,我是刑警寧澤关带,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站沼撕,受9級(jí)特大地震影響宋雏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜务豺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一磨总、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笼沥,春花似錦蚪燕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汹桦,卻和暖如春鲁驶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舞骆。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工钥弯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人督禽。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓寿羞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赂蠢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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