MVVM與ReactiveCocoa的運用(Part1)

本文翻譯自MVVM Tutorial with ReactiveCocoa

  • MVVM和數(shù)據(jù)綁定

MVVM模式依賴于數(shù)據(jù)綁定,能自動將對象屬性和UI controls相聯(lián)系是其框架級的特性.
舉個例子,在微軟的WPF框架里,ViewModel將TextField里的Text屬性和Username屬性綁定,如下所示:

<TextField Text=”{DataBinding Path=Username, Mode=TwoWay}”/>

WPF框架將兩個屬性綁定在一起.
TwoWay綁定確保ViewModel中的Username屬性改變時會為TextField的Text屬性改變做準備,而且可逆.例如用戶輸入時ViewModel的變化.
另一個例子是著名的基于MVVM的網(wǎng)頁框架Knockout,你可以在動作里看到相似的綁定特性:

<input data-bind=”value: username”/>   

上面將HTML元素的一個屬性和JavaScript模型綁定.
遺憾的是,iOS缺乏數(shù)據(jù)綁定的框架,但這正是ReactiveCocoa所扮演的角色:進行ViewModel連接"粘合"工作.
從iOS開發(fā)的角度來看MVVM模式,ViewController和其相關的UI(無論是nib、storyboard或者純代碼組成的View):

MVVMReactiveCocoa.png

....通過ReactiveCocoa將它們綁定在一起.
理論知識已經(jīng)補的差不多了吧?如果不熟悉,可以回去復讀一下.如果覺得還可以,那就開始編寫ViewModels吧.

  • 開始項目架構

首先下載一下初始項目:

項目用CocoaPods來管理依賴庫(如果你對CocoaPods不熟,這有你需要的教程].運行命令行pod install來獲取依賴庫,確保你會看到以下輸出:

$ pod install
Analyzing dependencies
Downloading dependencies
Installing LinqToObjectiveC (2.0.0)
Installing ReactiveCocoa (2.1.8)
Installing SDWebImage (3.6)
Installing objectiveflickr (2.0.4)
Generating Pods project
Integrating client project

你將會學到這些庫的很多用法.
初始項目已經(jīng)用view controllers和nib文件為你準備好了應用所需的視圖.打開CocoaPods所生成的RWTFlickrSearch.xcworkspace,運行后,你將看到其中的一個視圖:

first-launch.jpg

花些時間來熟悉下項目的結構:

EmptyInterface.png

Model和ViewModel group是空的,待會你將在里面添加文件.View Group包含以下內(nèi)容:

  • RWTFlickSearchViewController:程序的主界面,包含一個搜索text field和一個'Go'按鈕.
  • RWTRecentSearchItemTableViewCell:在主界面展示最近搜索結果的table cell.
  • RWTSearchResultsViewController:展示搜索結果Flickr圖片的table.
  • RWTSearchResultsTableViewCell:展示單個Flickr圖片的table cell.

好了,該開始編寫你的第一個view model嚕.

  • 你的首個ViewModel

在ViewModel組里添加一個名為RWTFlickrSearchModel的NSObject的子類.
打開此文件的頭文件,添加如下屬性:

@interface RWTFlickrSearchViewModel : NSObject
 
@property (strong, nonatomic) NSString *searchText;
@property (strong, nonatomic) NSString *title;
 
@end

SearchText屬性為text field里面輸入的文字,title屬性為navigation bar上的title.

打開RWTFlickrSearchViewModel.m添加如下代碼:

@implementation RWTFlickrSearchViewModel
 
- (instancetype)init {
  self = [super init];
  if (self) {
    [self initialize];
  }
  return self;
}
 
- (void)initialize {
  self.searchText = @"search text";
  self.title = @"Flickr Search";
}
 
@end

以上代碼用來進行ViewModel的初始化.
接下來將ViewModel和View連接.要記得View擁有對ViewModel的引用.當前給出的是相應ViewModel模型的View的初始化.
在RWTFlickrSearchViewController.h里導入ViewModel的頭文件:

#import "RWTFlickrSearchViewModel.h"

接著添加初始化方法:

@interface RWTFlickrSearchViewController : UIViewController
 - (instancetype)initWithViewModel:(RWTFlickrSearchViewModel *)viewModel;
 @end

在RWTFlickrSearchViewController.m里面添加一個私有屬性來控制UI outlets:

@property (weak, nonatomic) RWTFlickrSearchViewModel *viewModel;

繼而添加初始化方法:

- (instancetype)initWithViewModel:(RWTFlickrSearchViewModel *)viewModel {
  self = [super init];
  if (self) {
    _viewModel = viewModel;
  }
  return self;
}

這將存儲一個View對ViewModel的引用.
在viewDidLoad的底部增加:

[self bindViewModel];

然后實現(xiàn)以下方法:

- (void)bindViewModel {
  self.title = self.viewModel.title;
  self.searchTextField.text = self.viewModel.searchText;
}

上面的代碼當UI初始化和ViewModel狀態(tài)給View是調(diào)用.
最后是創(chuàng)建個ViewModel的實例以供View使用.
RWTAppDelegate.m增加如下導入:

#import "RWTFlickrSearchViewModel.h"

添加一個私有屬性:

@property (strong, nonatomic) RWTFlickrSearchViewModel *viewModel;

你會發(fā)現(xiàn)這個類已經(jīng)有個方法createInitialViewController,更新它的實現(xiàn)代碼:

- (UIViewController *)createInitialViewController {
  self.viewModel = [RWTFlickrSearchViewModel new];
  return [[RWTFlickrSearchViewController alloc] initWithViewModel:self.viewModel];
}

以上代碼用來創(chuàng)建一個ViewModel的新實例,繼而構建和返回View.作用為初始化應用的navigation controller.
運行程序,你將看到View有一些狀態(tài)!

ViewWithState-333x500.png

恭喜哦,這是你的第一個ViewModel.請克制你的興奮,還有許多要學;]
也許你覺察到還木有用到ReactiveCocoa.當前,如果你在搜索text field輸入內(nèi)容將不會傳遞到ViewModel.

  • 檢測有效的搜索狀態(tài)

本章節(jié),你將使用ReactiveCocoa綁定ViewModel和View以使搜索text field和按鈕能和ViewModel連接.
RWTFlickrSearchViewController.m更新bindViewModel;

- (void)bindViewModel {
  self.title = self.viewModel.title;
  RAC(self.viewModel, searchText) = self.searchTextField.rac_textSignal;
}

通過ReactiveCocoa的一個category給UITextField的類添加了rac_textSignal屬性.這是個text field當前文本內(nèi)容更新的信號量.
RAC宏是個綁定操作,上面的代碼通過rac_textSignal來監(jiān)測輸入的狀態(tài)實時更新viewModel中的searchText屬性.
簡言之,就是確保seachText屬性能實時反映出當前的UI狀態(tài).如果你覺得難以理解,那么最好去讀讀之前的兩篇ReactiveCocoa tutorials!
搜索按鈕只有在用戶輸入合法的文本時才能使用.我們設定只有在輸入超過三個字符時才能進行搜索.
在RWTFlickrSearchViewModel.m里增加如下導入:

#import <ReactiveCocoa/ReactiveCocoa.h>

更新初始化方法里的內(nèi)容:

- (void)initialize {
  self.title = @"Flickr Search";
 
  RACSignal *validSearchSignal =
    [[RACObserve(self, searchText)
      map:^id(NSString *text) {
         return @(text.length > 3);
      }]
      distinctUntilChanged];
 
  [validSearchSignal subscribeNext:^(id x) {
    NSLog(@"search text is valid %@", x);
  }];
}

運行程序,在text field里持續(xù)輸入內(nèi)容.你將從日志中看到text在合法和不合法狀態(tài)間改變:

2014-05-27 18:03:26.299 RWTFlickrSearch[13392:70b] search text is valid 0
2014-05-27 18:03:28.379 RWTFlickrSearch[13392:70b] search text is valid 1
2014-05-27 18:03:29.811 RWTFlickrSearch[13392:70b] search text is valid 0

上面的代碼使用RACObserve宏在ViewModel中的searchText屬性創(chuàng)建一個信號量(這就是ReactiveCocoa對KVO的包裝).一個map操作將text轉換為真假值.
最后distinctUnitlChanges使信號量只有在值狀態(tài)改變時發(fā)出.
截止現(xiàn)在你看到的是用ReactiveCocoa將View綁定到ViewModel,使之得到同步.另外,ReactiveCocoa經(jīng)常在ViewModel里來監(jiān)測它本身狀態(tài)來進行其它操作.
這種類型貫穿本MVVM教程.ReactiveCocoa用來綁定View和ViewModel,但在應用其它layers里也會有用.

  • 增加一個搜索指令

本章節(jié),你將使用validSearchSignal做更多事情:創(chuàng)建一個綁定View的指令.
RWTFlickrSearchViewModel.h增加如下導入:

#import <ReactiveCocoa/ReactiveCocoa.h>

添加屬性:

@property (strong, nonatomic) RACCommand *executeSearch;

RACCommand是ReactiveCocoa中呈現(xiàn)UI動作的組件.它包含一個來表示UI動作結果、當前狀態(tài)衷笋、標明動作是否被執(zhí)行的信號量.
在RWTFlickrSearchViewModel.m里的initialize方法的尾部增加:

self.executeSearch =
  [[RACCommand alloc] initWithEnabled:validSearchSignal
    signalBlock:^RACSignal *(id input) {
      return  [self executeSearchSignal];
    }];

當validSearchSignal為真時創(chuàng)建的指令為可用.
接著添加如下方法來提供創(chuàng)建執(zhí)行指令的信號量:

- (RACSignal *)executeSearchSignal {
  return [[[[RACSignal empty]
           logAll]
           delay:2.0]
           logAll];
}

在這個方法中將執(zhí)行一些業(yè)務邏輯作為執(zhí)行命令的結果,并會通過信號異步地返回結果.
目前只完成了一個虛擬的執(zhí)行情況;空信號立即完成.延遲操作增加了完成事件返回后的兩秒延遲.用來使代碼看起來更加真實.
最后一步為將這個指令添加到View中.打開RWTFlickrSearchViewController.m在bindViewModel方法的尾部添加:

self.searchButton.rac_command = self.viewModel.executeSearch;

上面的rac_command屬性為ReactiveCocoa給UIButton添加的擴展.用來確保button點擊后指定的命令執(zhí)行,并且按鈕的可用狀態(tài)反應了命令的可用狀態(tài).
運行,輸入一些文本,點擊Go:

GoButtonEnabled-333x500.png

你會看到按鈕只有在輸入文本大于三個字符時才可用.而且當年你點擊按鈕后兩秒內(nèi)不可用,當執(zhí)行完信號量的時候才又變得可用了. 在console里,你將看到空信號量立即完成,兩秒后延遲操作執(zhí)行:

09:31:25.728 RWTFlickrSearch ... name: +empty completed
09:31:27.730 RWTFlickrSearch ... name: [+empty] -delay: 2.0 completed

是不是超酷之宿?

Girl學iOS100天 第17天

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凡伊,一起剝皮案震驚了整個濱河市糠馆,隨后出現(xiàn)的幾起案子橘茉,更是在濱河造成了極大的恐慌,老刑警劉巖展鸡,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屿衅,死亡現(xiàn)場離奇詭異,居然都是意外死亡莹弊,警方通過查閱死者的電腦和手機涤久,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忍弛,“玉大人响迂,你說我怎么就攤上這事∠妇危” “怎么了蔗彤?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疯兼。 經(jīng)常有香客問我然遏,道長,這世上最難降的妖魔是什么吧彪? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任待侵,我火速辦了婚禮,結果婚禮上诫给,老公的妹妹穿的比我還像新娘香拉。我一直安慰自己啦扬,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布凫碌。 她就那樣靜靜地躺著扑毡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盛险。 梳的紋絲不亂的頭發(fā)上瞄摊,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音苦掘,去河邊找鬼换帜。 笑死,一個胖子當著我的面吹牛鹤啡,可吹牛的內(nèi)容都是我干的惯驼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼祟牲!你這毒婦竟也來了隙畜?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤说贝,失蹤者是張志新(化名)和其女友劉穎议惰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乡恕,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡言询,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了傲宜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倍试。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛋哭,靈堂內(nèi)的尸體忽然破棺而出县习,到底是詐尸還是另有隱情,我是刑警寧澤谆趾,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布躁愿,位于F島的核電站,受9級特大地震影響沪蓬,放射性物質(zhì)發(fā)生泄漏彤钟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一跷叉、第九天 我趴在偏房一處隱蔽的房頂上張望逸雹。 院中可真熱鬧,春花似錦云挟、人聲如沸梆砸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帖世。三九已至,卻和暖如春沸枯,著一層夾襖步出監(jiān)牢的瞬間日矫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工绑榴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哪轿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓翔怎,卻偏偏與公主長得像窃诉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

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