1. 程式人生 > >MVVM 與 ReactiveCocoa 的運用(1)

MVVM 與 ReactiveCocoa 的運用(1)

  • MVVM和資料繫結

MVVM模式依賴於資料繫結,能自動將物件屬性和UI controls相聯絡是其框架級的特性.

舉個例子,在微軟的WPF框架裡,ViewModel將TextField裡的Text屬性和Username屬性繫結,如下所示:

Objective-C
1 <TextFieldText={DataBinding
Path=Username,Mode=TwoWay}/>

WPF框架將兩個屬性繫結在一起.

TwoWay繫結確保ViewModel中的Username屬性改變時會為TextField的Text屬性改變做準備,而且可逆.例如使用者輸入時ViewModel的變化.

另一個例子是著名的基於MVVM的網頁框架Knockout,你可以在動作裡看到相似的繫結特性:

Objective-C
1 <input data-bind=value: username/>

上面將HTML元素的一個屬性和JavaScript模型繫結.

遺憾的是,iOS缺乏資料繫結的框架,但這正是ReactiveCocoa所扮演的角色:進行ViewModel連線”粘合”工作.

從iOS開發的角度來看MVVM模式,ViewController和其相關的UI(無論是nib、storyboard或者純程式碼組成的View):

MVVMReactiveCocoa.png

….通過ReactiveCocoa將它們繫結在一起.

理論知識已經補的差不多了吧?如果不熟悉,可以回去復讀一下.如果覺得還可以,那就開始編寫ViewModels吧.

  • 開始專案架構

首先下載一下初始專案:

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

Objective-C
123456789 $pod installAnalyzingdependenciesDownloadingdependenciesInstallingLinqToObjectiveC(2.0.0)InstallingReactiveCocoa(2.1.8)InstallingSDWebImage(3.6)Installingobjectiveflickr(2.0.4)GeneratingPodsprojectIntegratingclient project

你將會學到這些庫的很多用法.

初始專案已經用view controllers和nib檔案為你準備好了應用所需的檢視.開啟CocoaPods所生成的RWTFlickrSearch.xcworkspace,執行後,你將看到其中的一個檢視:

first-launch.jpg

花些時間來熟悉下專案的結構:

EmptyInterface.png

Model和ViewModel group是空的,待會你將在裡面新增檔案.View Group包含以下內容:

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

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

  • 你的首個ViewModel

在ViewModel組裡新增一個名為RWTFlickrSearchModel的NSObject的子類.

開啟此檔案的標頭檔案,新增如下屬性:

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

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

開啟RWTFlickrSearchViewModel.m新增如下程式碼:

Objective-C
12345678910111213141516 @implementationRWTFlickrSearchViewModel-(instancetype)init{self=[superinit];if(self){[selfinitialize];}returnself;}-(void)initialize{self.searchText=@"search text";self.title=@"Flickr Search";}@end

以上程式碼用來進行ViewModel的初始化.

接下來將ViewModel和View連線.要記得View擁有對ViewModel的引用.當前給出的是相應ViewModel模型的View的初始化.

在RWTFlickrSearchViewController.h裡匯入ViewModel的標頭檔案:

Objective-C
1 #import "RWTFlickrSearchViewModel.h"
接著新增初始化方法: Objective-C
123 @interface RWTFlickrSearchViewController : UIViewController-(instancetype)initWithViewModel:(RWTFlickrSearchViewModel*)viewModel;@end

在RWTFlickrSearchViewController.m裡面新增一個私有屬性來控制UI outlets:
Objective-C
1 @property(weak,nonatomic)RWTFlickrSearchViewModel*viewModel;

繼而新增初始化方法:
Objective-C
1234567 -(instancetype)initWithViewModel:(RWTFlickrSearchViewModel*)viewModel{self=[superinit];if(self){_viewModel=viewModel;}returnself;}

這將儲存一個View對ViewModel的引用.

在viewDidLoad的底部增加:

Objective-C
1 [selfbindViewModel];

然後實現以下方法:

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

上面的程式碼當UI初始化和ViewModel狀態給View是呼叫.

最後是建立個ViewModel的例項以供View使用.

RWTAppDelegate.m增加如下匯入:

Objective-C
1 #import "RWTFlickrSearchViewModel.h"

新增一個私有屬性:

Objective-C
1 @property(strong,nonatomic)RWTFlickrSearchViewModel*viewModel;

你會發現這個類已經有個方法createInitialViewController,更新它的實現程式碼:

Objective-C
1234 -(UIViewController*)createInitialViewController{self.viewModel=[RWTFlickrSearchViewModelnew];return[[RWTFlickrSearchViewControlleralloc] initWithViewModel:self.viewModel];}

以上程式碼用來建立一個ViewModel的新例項,繼而構建和返回View.作用為初始化應用的navigation controller.
執行程式,你將看到View有一些狀態!

ViewWithState-333×500.png

恭喜哦,這是你的第一個ViewModel.請剋制你的興奮,還有許多要學;]

也許你覺察到還木有用到ReactiveCocoa.當前,如果你在搜尋text field輸入內容將不會傳遞到ViewModel.

  • 檢測有效的搜尋狀態

本章節,你將使用ReactiveCocoa繫結ViewModel和View以使搜尋text field和按鈕能和ViewModel連線.

RWTFlickrSearchViewController.m更新bindViewModel;

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

通過ReactiveCocoa的一個category給UITextField的類添加了rac_textSignal屬性.這是個text field當前文字內容更新的訊號量.

RAC巨集是個繫結操作,上面的程式碼通過rac_textSignal來監測輸入的狀態實時更新viewModel中的searchText屬性.

簡言之,就是確保seachText屬效能實時反映出當前的UI狀態.如果你覺得難以理解,那麼最好去讀讀之前的兩篇ReactiveCocoa tutorials!

搜尋按鈕只有在使用者輸入合法的文字時才能使用.我們設定只有在輸入超過三個字元時才能進行搜尋.

在RWTFlickrSearchViewModel.m裡增加如下匯入:

Objective-C
1 #import <ReactiveCocoa/ReactiveCocoa.h>

更新初始化方法裡的內容:

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

執行程式,在text field裡持續輸入內容.你將從日誌中看到text在合法和不合法狀態間改變:

Objective-C
123 2014-05-2718:03:26.299RWTFlickrSearch[13392:70b] searchtext is valid02014-05-2718:03:28.379RWTFlickrSearch[13392:70b] searchtext is valid12014-05-2718:03:29.811RWTFlickrSearch[13392:70b] searchtext is valid0

上面的程式碼使用RACObserve巨集在ViewModel中的searchText屬性建立一個訊號量(這就是ReactiveCocoa對KVO的包裝).一個map操作將text轉換為真假值.

最後distinctUnitlChanges使訊號量只有在值狀態改變時發出.

截止現在你看到的是用ReactiveCocoa將View繫結到ViewModel,使之得到同步.另外,ReactiveCocoa經常在ViewModel裡來監測它本身狀態來進行其它操作.

這種型別貫穿本MVVM教程.ReactiveCocoa用來繫結View和ViewModel,但在應用其它layers裡也會有用.

  • 增加一個搜尋指令

本章節,你將使用validSearchSignal做更多事情:建立一個繫結View的指令.

RWTFlickrSearchViewModel.h增加如下匯入:

Objective-C
1 #import <ReactiveCocoa/ReactiveCocoa.h>

新增屬性:

Objective-C
1 @property(strong,nonatomic)RACCommand*executeSearch;