RAC+MVVM的項(xiàng)目實(shí)例演練

ps:最近學(xué)習(xí)了ReactiveCocoa(RAC)脚线,就用這個(gè)結(jié)合MVVM的思想弄了個(gè)小項(xiàng)目屎媳,項(xiàng)目源碼已經(jīng)上傳到GitHub上,有興趣的同胞可以下載下來(lái)阔逼,源碼地址兆衅,下面我就抽出一個(gè)界面來(lái)介紹一下如何使用RAC+MVVM,例子是經(jīng)典的tableView類型。

先附上一張結(jié)構(gòu)圖

屏幕快照 2017-10-12 16.32.35.png

從圖中可以清晰地看到項(xiàng)目結(jié)構(gòu):MVVM 各自對(duì)應(yīng)的位置,具體的MVVM原理羡亩,網(wǎng)上資料一大堆摩疑,這里就不做贅述,我簡(jiǎn)要說(shuō)一下夕春,各自的部分都做了哪些功能:

M:這個(gè)不用說(shuō)未荒,是model層,我這里處理比較簡(jiǎn)單及志,只是單純的用來(lái)處理數(shù)據(jù)轉(zhuǎn)模型

V:view,主要用于數(shù)據(jù)展現(xiàn)

VM:這里是MVVM出現(xiàn)的重點(diǎn)所在片排,它主要用來(lái)處理數(shù)據(jù)分析和一些業(yè)務(wù)邏輯處理,我這里是將網(wǎng)絡(luò)請(qǐng)求以及告訴view層展現(xiàn)數(shù)據(jù)的業(yè)務(wù)都放在了這里(這里我在做的時(shí)候也有疑問(wèn)速侈,告訴view的動(dòng)作究竟是由controller做好率寡,還是放到vm中好,最后看了一些資料倚搬,覺(jué)得放在vm中更加理想冶共,前提是在控制器中就建立好vm和view的聯(lián)系,也就是將view綁定到vm中)每界,下面的例子可以看到捅僵。

最后就說(shuō)一下Controller了,在MVC中Controller是用來(lái)溝通M和V的眨层,它既要知道M何時(shí)發(fā)生了改變庙楚,又要隨時(shí)準(zhǔn)備告訴V去改變視圖展現(xiàn),相應(yīng)的一些業(yè)務(wù)邏輯處理也只能丟到C中趴樱,導(dǎo)致了C的臃腫馒闷,在MVVM中,可以極大地去減輕控制器的負(fù)擔(dān)叁征,從某種程度上纳账,比如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)分析以及一些業(yè)務(wù)邏輯都可以放到VM中捺疼。C這個(gè)時(shí)候也需要充當(dāng)中間人的角色疏虫,只不過(guò)它不用再去監(jiān)控M層變化,也不需要告訴View層改變數(shù)據(jù)展示啤呼,這些都可以由VM來(lái)代勞议薪。我看過(guò)一篇文章,說(shuō)的是控制器只需要處理必須放到控制器的邏輯媳友,例如頁(yè)面跳轉(zhuǎn)斯议,view的初始化,VM的初始化等醇锚,我深以為然哼御,在這個(gè)例子中我也是這樣處理的坯临。

下面,先開始從控制器層說(shuō)起:

1. 控制器: WLHomeController

實(shí)現(xiàn)功能 : 首頁(yè)的內(nèi)容是類似于新聞首頁(yè)恋昼,既有頂部標(biāo)簽(我這里處理的較簡(jiǎn)單看靠,頂部沒(méi)有滾動(dòng)選擇功能),內(nèi)容視圖又可以左右滾動(dòng)查看,同時(shí)上下可以聯(lián)動(dòng)液肌,又能保證再次回到出現(xiàn)過(guò)得界面不會(huì)再次自動(dòng)發(fā)送網(wǎng)絡(luò)請(qǐng)求挟炬。

-(WLTopTagView *)topTagView{
    if (!_topTagView){
        _topTagView = [[WLTopTagView alloc] initWithFrame:CGRectMake(0, kNavigationBarH, self.view.frame.size.width, 30)];
        [self.view addSubview:_topTagView];
    }
    return _topTagView;
}

-(UIScrollView *)mainScrollView{
    if (!_mainScrollView){
        _mainScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.topTagView.frame) + 1,kScreenW, kScreenH -CGRectGetMaxY(self.topTagView.frame) - 1)];
        _mainScrollView.backgroundColor = [UIColor whiteColor];
        _mainScrollView.pagingEnabled = YES;
        _mainScrollView.delegate = self;
        [self.view addSubview:_mainScrollView];
    }
    return _mainScrollView;
}

- (NSMutableArray *)listTableViewArray {
    if (!_listTableViewArray) {
        _listTableViewArray = [NSMutableArray array];
    }
    return _listTableViewArray;
}

-(WLHomtTopViewModel *)topViewModel{
    if (!_topViewModel){
        _topViewModel = [[WLHomtTopViewModel alloc] init];
    }
    return _topViewModel;
}

-(NSMutableArray *)viewModelArray{
    if (!_viewModelArray){
        _viewModelArray = [NSMutableArray array];
    }
    return _viewModelArray;
}

這里是懶加載初始化必要視圖及數(shù)據(jù).

設(shè)計(jì)方案 : 我采用的是UIScrollView + UITableView的結(jié)構(gòu),有興趣的同胞可以嘗試一下UICollectionView + UITableView 的結(jié)構(gòu)來(lái)實(shí)現(xiàn)
我這里沒(méi)有采用復(fù)用幾個(gè)tableView的思想嗦哆,這里是可以優(yōu)化的點(diǎn)谤祖,可以復(fù)用兩個(gè)或者三個(gè)tableView去節(jié)約內(nèi)存。

將view和viewModel綁定:

       [viewModel bindViewToViewModel:tableView]; 

主要是通過(guò)viewModel中提供的接口

- (void)bindViewToViewModel:(UIView *)view {
    self.tableView = (UITableView *)view;
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.tableView registerNib:[UINib nibWithNibName:@"WLHomeListCell" bundle:nil] forCellReuseIdentifier:@"listCell"];
}

題外話:

- (void)bindViewToViewModel:(UIView *)view

再好一點(diǎn)的做法是創(chuàng)建一個(gè)VM基類老速,把這個(gè)方法抽出來(lái)粥喜,所有的VM都繼承于這個(gè)基類,分別取實(shí)現(xiàn)這個(gè)方法橘券。我比較懶额湘,一開始沒(méi)考慮到這種情況,后來(lái)就不想改了旁舰,湊合著看吧

到這里你可能會(huì)有疑問(wèn)锋华,那么如何去使用RAC呢?下面我就來(lái)簡(jiǎn)要說(shuō)一下如何去用箭窜,如何建立起控制器和VM之間的關(guān)系:

2.如何運(yùn)用RAC+VM

@property (nonatomic,strong,readonly) RACCommand *homeListCommand;
連接VM和C的東西就是它 毯焕,關(guān)于RAC的原理和實(shí)現(xiàn)我也講不出來(lái),只會(huì)用绽快,如果你想了解芥丧,可以去搜索一些RAC的資料紧阔,自行學(xué)習(xí)撒~~~

在VM中需要這樣做:

//獲取首頁(yè)列表數(shù)據(jù)
- (void)requestHomeListInfo {
    @weakify(self);
    _homeListCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        @strongify(self);
        if (!self.firstLoadData) {
            return [RACSignal empty];
        }
        self.firstLoadData = NO;
        RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [[WLNetworkTool sharedInstance] loadHomeListData:[input intValue] success:^(id response) {
                [subscriber sendNext:response];
                [subscriber sendCompleted];
            } failure:^(NSError *error) {
                [subscriber sendError:error];
                [subscriber sendCompleted];
            }];
            return nil;
        }];
        return [requestSignal map:^id(NSDictionary *value) {
            NSArray *data = value[@"data"][@"items"];
            NSArray *modelsArray = [[data.rac_sequence map:^id(id value) {
                return [WLHomeListModel mj_objectWithKeyValues:value];
            }] array];
            
            //這一步刷新列表可以放到這里坊罢,也可以放到控制器里面
            NSLog(@"請(qǐng)求首頁(yè)列表數(shù)據(jù)成功 %@",modelsArray);
            if (modelsArray && modelsArray.count > 0) {
                [self.homeListArray removeAllObjects];
                [self.homeListArray addObjectsFromArray:modelsArray];
                [self.tableView reloadData];
            }
            return modelsArray;
        }];
    }];
}

RACCommand內(nèi)部是擁有一個(gè)signal的,我們所謂的網(wǎng)絡(luò)請(qǐng)求也就是在這個(gè)里面去弄擅耽,至于何時(shí)觸發(fā)活孩,下面再介紹,現(xiàn)在先來(lái)把這一段給簡(jiǎn)要說(shuō)一下:
內(nèi)部信號(hào)requestSignal 里面實(shí)現(xiàn)的東西需要用[requestSignal map:^id(NSDictionary *value)觸發(fā)乖仇,你打斷點(diǎn)可以看到憾儒,先執(zhí)行的是[requestSignal map:^id(NSDictionary *value),而后信號(hào)激活變成熱信號(hào),才會(huì)去執(zhí)行信號(hào)里面的內(nèi)容乃沙,也就是網(wǎng)絡(luò)請(qǐng)求起趾。
網(wǎng)絡(luò)請(qǐng)求成功后,會(huì)執(zhí)行map里面的內(nèi)容警儒,我這里為了熟悉RAC,特意用了map去對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換训裆,想省事眶根,可以在這里直接用MJExtension就行,最后才會(huì)走到控制器中訂閱的block之中

 [requestSignal subscribeNext:^(NSArray *x) {
//            self.currentTableView = self.listTableViewArray[index];
            NSLog(@"請(qǐng)求首頁(yè)列表數(shù)據(jù)成功 %@",x);
//            if (x && x.count > 0) {
//                [viewModel.homeListArray removeAllObjects];
//                [viewModel.homeListArray addObjectsFromArray:x];
//                [self.currentTableView reloadData];
//            }
        }];

那么這個(gè)command又是什么時(shí)候執(zhí)行的呢边琉,這就需要控制器去觸發(fā)執(zhí)行時(shí)間了属百,其實(shí)很簡(jiǎn)單,就一句話
[viewModel.homeListCommand execute:@(model.ID)];
這就是整個(gè)觸發(fā)流程变姨,簡(jiǎn)單說(shuō)可以理解為以下幾個(gè)步驟:
1.在vm中定義command
2.在控制器中將view和vm綁定
3.在控制器中觸發(fā)command執(zhí)行時(shí)機(jī)
4.在vm中進(jìn)行網(wǎng)絡(luò)請(qǐng)求并處理數(shù)據(jù)族扰,通知view刷新數(shù)據(jù)

這個(gè)是控制器和vm之間的通信,通過(guò)信號(hào)機(jī)制解決定欧,那么vm和v之間是如何實(shí)現(xiàn)使用RAC的呢渔呵?這就需要RAC中的訂閱者,顧名思義忧额,訂閱之就是先訂閱對(duì)象厘肮,然后在合適的時(shí)機(jī)觸發(fā)執(zhí)行條件,那么訂閱的內(nèi)容就會(huì)執(zhí)行睦番,這個(gè)比較類似于OC中的block类茂,其實(shí)就是block,先保存要執(zhí)行的block塊托嚣,然后在某個(gè)時(shí)間點(diǎn)觸發(fā)執(zhí)行block操作巩检,訂閱者的實(shí)現(xiàn)也是類似。

3.VM和V之間通信

首先你需要在V中有一個(gè)訂閱者 @property (nonatomic,strong) RACSubject *cellSubject;
這個(gè)訂閱者是被V擁有的示启,觸發(fā)時(shí)機(jī)是由外界觸發(fā)兢哭,所以,在V中需要這樣寫:

 self.cellSubject = [RACSubject subject];
    @weakify(self);
    [self.cellSubject subscribeNext:^(WLHomeListModel *model) {
        @strongify(self);
//        NSLog(@"傳送過(guò)來(lái)模型了");
        self.descLabel.text = model.title;
        self.likeControl.text = [NSString stringWithFormat:@"%d",model.likes_count];
        self.likeControl.image = model.liked ? [UIImage imageNamed:@"content-details_like_selected_16x16_"] : [UIImage imageNamed:@"Feed_FavoriteIcon_17x17_"];
        [self.coverImageView sd_setImageWithURL:[NSURL URLWithString:model.cover_image_url] placeholderImage:[UIImage imageNamed:@"PlaceHolderImage_small_31x26_"]];
        self.model = model;
    }];

而在VM中需要這樣觸發(fā):

WLHomeListCell *cell = [tableView dequeueReusableCellWithIdentifier:@"listCell" forIndexPath:indexPath];
    [cell.cellSubject sendNext:self.homeListArray[indexPath.row]];

這是不是很像block的使用邏輯呢夫嗓?當(dāng)然原理是不一樣的迟螺,為了方便理解,可以這樣認(rèn)為~~~

那么舍咖,有的需求是v中點(diǎn)擊某個(gè)按鈕矩父,需要告訴控制器或者別的類去做相應(yīng)的操作,這個(gè)時(shí)候怎么辦呢排霉?也需要訂閱者參與:
不過(guò)不同的是窍株,訂閱者的初始化操作不在V中,而是在需要被通知的那個(gè)類中攻柠,這個(gè)例子中就是VM球订,V是負(fù)責(zé)激活訂閱者的,那么我可不可以這樣理解:誰(shuí)是需要被告知執(zhí)行某個(gè)任務(wù)的對(duì)象瑰钮,它就要?jiǎng)?chuàng)建訂閱者冒滩,誰(shuí)需要激活訂閱者,誰(shuí)就要執(zhí)行send操作浪谴?這只是我個(gè)人的理解开睡,有不對(duì)的地方可以指出祈搜,能讓我更加理解RAC+MVVM的操作>-<

V中有一個(gè)喜歡按鈕,點(diǎn)擊喜歡士八,需要作出相應(yīng)的處理:
在V中:
@property (nonatomic,strong) RACSubject *likeSubject;
點(diǎn)擊喜歡按鈕后,激活訂閱者:

 [[self.likeControl rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        @strongify(self);
        [self.likeSubject sendNext:@(self.model.ID)];
        [self.likeSubject sendCompleted];
    }];

VM中創(chuàng)建訂閱者容燕,并保存訂閱者需要執(zhí)行的操作:

  [cell.likeSubject subscribeNext:^(id x) {
        NSLog(@"點(diǎn)擊了喜歡 %d",[x intValue]);
    }];

這樣也就完成了VM和V之間的通訊(正向反向都有)

這只是我個(gè)人看了一些RAC資料后練習(xí)的小項(xiàng)目,里面肯定有很多問(wèn)題婚度,如果你有疑問(wèn)或者對(duì)RAC+MVVM有別的理解的蘸秘,很高興你能為我指正,感激不盡蝗茁,最后再附上源代碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末醋虏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哮翘,更是在濱河造成了極大的恐慌颈嚼,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭寺,死亡現(xiàn)場(chǎng)離奇詭異阻课,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)艰匙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門限煞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人员凝,你說(shuō)我怎么就攤上這事署驻。” “怎么了健霹?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵旺上,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我糖埋,道長(zhǎng)宣吱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任阶捆,我火速辦了婚禮凌节,結(jié)果婚禮上钦听,老公的妹妹穿的比我還像新娘洒试。我一直安慰自己,他們只是感情好朴上,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布垒棋。 她就那樣靜靜地躺著,像睡著了一般痪宰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贸铜,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天璧亮,我揣著相機(jī)與錄音,去河邊找鬼扮饶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乍构,可吹牛的內(nèi)容都是我干的甜无。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼哥遮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼岂丘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起眠饮,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奥帘,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后仪召,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寨蹋,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年扔茅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钥庇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咖摹,死狀恐怖评姨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萤晴,我是刑警寧澤吐句,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站店读,受9級(jí)特大地震影響嗦枢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屯断,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一文虏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧殖演,春花似錦氧秘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至彼棍,卻和暖如春灭忠,著一層夾襖步出監(jiān)牢的瞬間膳算,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工弛作, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涕蜂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓映琳,卻偏偏與公主長(zhǎng)得像宇葱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刊头,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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