本文翻譯自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):
....通過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,運行后,你將看到其中的一個視圖:
花些時間來熟悉下項目的結構:
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)!
恭喜哦,這是你的第一個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:
你會看到按鈕只有在輸入文本大于三個字符時才可用.而且當年你點擊按鈕后兩秒內(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天