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

MVVM 與 ReactiveCocoa 的運用(2)

  • 繫結,繫結,繫結(重要的實情說三遍)

RACCommand能實時地更新search按鈕的狀態,但是時候來處理activity indicator的可見狀態了.
RACCommand擁有一個執行的屬性,它是用來表示命令開始和結束執行時反應真假事件的訊號量.你可以通過這個訊號量來反映程式中當前命令執行的狀態.
在RWTFlickrSearchViewController.m的bindViewModel的尾部新增:

Objective-C
1 RAC([UIApplicationsharedApplication],networkActivityIndicatorVisible)=self.viewModel.executeSearch.executing;

以上程式碼用來將UIApplication中的networkActivityIndicatorVisible屬性和命令執行訊號繫結.用來保證當命令執行時,小的網路啟用狀態標誌在status bar裡顯示.
下一步,新增:

Objective-C
1 RAC(self.loadingIndicator,hidden)=[self.viewModel.executeSearch.executing not];

當指令執行後,載入標誌將會被隱藏;這和你剛剛繫結的屬性相反.
ReactiveCocoa已經為我們提供了不執行的相反的訊號.最後,新增如下程式碼:

Objective-C
1234 [self.viewModel.executeSearch.executionSignals  subscribeNext:^(idx){[self.searchTextField resignFirstResponder];}];

上面的程式碼用來保證當命令執行時鍵盤會隱藏.executionSignals屬性用來在命令執行時實時地發出訊號.
這是個signals中的一個signal屬性(前面教程裡有介紹).當一個新的命令執行的時候就會被建立和執行,隱藏鍵盤.
執行程式,來驗證以上程式碼的執行.

  • Model呢?

到現在為止,你已經定義了一個View(RWTFlickrSearchViewController)和ViewModel(RWTFlickrSearchViewModel),但是,怎麼木有Model呢?
答案很簡單:就是還沒有啊!
當前app使用者點選搜尋按鈕後就會執行命令,但卻沒有實現什麼.
我們需要實現的是利用當前輸入的搜尋文字通過ViewModel在Flickr進行搜尋,繼而返回相匹配的圖片列表.
你可以將此邏輯直接放在ViewModel裡,但相信我,你會後悔的!如果是個view controller,我到時強烈你這麼做.
View Model擁有UI狀態的屬性,而且還能夠執行命令(經常為UI上的動作方法).通過使用者的互動來管理改變UI狀態.
然而,並不表示這些互動實際的業務邏輯應該在View Model裡面.而這應該是Model的工作.
下一步,將會給應用增加Model層.
在Model group裡新增一個名為RWTFlickrSearch的新協議並提供瞭如下的方法:

Objective-C
12345678 #import <ReactiveCocoa/ReactiveCocoa.h>@import Foundation;@protocolRWTFlickrSearch<NSObject>-(RACSignal*)flickrSearchSignal:(NSString*)searchString;@end

這個協議定義了Model層的初始方法,用來將負責搜尋Flickr的任務從ViewModel裡移出.
接下來,在同一group裡建立一個名為RWTFlickrSearchImpl的NSObject的子類.並使其遵從剛才的協議:

Objective-C
123456 @import Foundation;#import "RWTFlickrSearch.h"@interfaceRWTFlickrSearchImpl: NSObject<RWTFlickrSearch>@end

在RWTFlickrSearchImpl.m裡新增以下程式碼:

Objective-C
12345678910 @implementationRWTFlickrSearchImpl-(RACSignal*)flickrSearchSignal:(NSString*)searchString{return[[[[RACSignalempty]            logAll]            delay:2.0]            logAll];}@end

是不是覺得似曾相識?如果是的,那是因為這個同一’虛擬’的實現曾經位於ViewModel裡.
下一步是要在ViewModel裡使用Model層.在ViewModel group裡新增一個名為RWTViewModelServices的新協議:

Objective-C
12345678 @import Foundation;#import "RWTFlickrSearch.h"@protocolRWTViewModelServices<NSObject>-(id<RWTFlickrSearch>)getFlickrSearchService;@end

這個協議定義了ViewModel獲得對RWTFlickrSearch協議引用的方法.
在RWTFlickrSearchViewModel.h匯入這個新協議:

Objective-C
1 #import "RWTViewModelServices.h"

更新initializer來將它作為引數:

Objective-C
1 -(instancetype) initWithServices:(id<RWTViewModelServices>)services;

在RWTFlickrSearchViewModel.m裡新增一個類擴充套件和一個私有屬性來儲存對view model services的引用:

Objective-C
12345 @interfaceRWTFlickrSearchViewModel()@property(nonatomic,weak)id<RWTViewModelServices>services;@end

在同一檔案裡更新initializer:

Objective-C
12345678 -(instancetype) initWithServices:(id<RWTViewModelServices>)services{self=[superinit];if(self){_services=services;[selfinitialize];}returnself;}

以上用來儲存對services的引用.
最後,更新executeSearchSignal方法:

Objective-C
1234 -(RACSignal*)executeSearchSignal{<span class="hljs-keyword">return</span>[[<span class="hljs-keyword">self</span><span class="hljs-variable">.services</span>getFlickrSearchService]           flickrSearchSignal:<span class="hljs-keyword">self</span><span class="hljs-variable">.searchText</span>];}

上面的方法代理了model來實現搜尋.
最後一步是將Model和ViewModel相連.
在RWTFlickrSearch專案的’root’ group裡新增一個名為RWTViewModelServiceImpl的NSObject的子類.在RWTViewModelServicesImpl.h裡新增遵從RWTViewModelServices協議:

Objective-C
123456 @import Foundation;#import "RWTViewModelServices.h"@interfaceRWTViewModelServicesImpl: NSObject<RWTViewModelServices>@end

在RWTViewModelServicesImpl.m實現:

Objective-C
1234567891011121314151617181920212223 #import "RWTViewModelServicesImpl.h"#import "RWTFlickrSearchImpl.h"@interfaceRWTViewModelServicesImpl()@property(strong,nonatomic)RWTFlickrSearchImpl*searchService;@end@implementationRWTViewModelServicesImpl-(instancetype)init{if(self=[superinit]){_searchService=[RWTFlickrSearchImplnew];}returnself;}-(id<RWTFlickrSearch>)getFlickrSearchService{returnself.searchService;}@end

這個類建立了一個RWTFlickrSearchImpl的例項,Model層服務Flickr搜尋,將其提供給ViewModel upon 請求.
最後,在RWTAppDelegate.m裡匯入:

Objective-C
1 #import "RWTViewModelServicesImpl.h"

新增一個私有屬性:

Objective-C
1 @property(strong,nonatomic)RWTViewModelServicesImpl*viewModelServices;

更新createInitialViewController方法:

Objective-C
1234567 -(UIViewController*)createInitialViewController{self.viewModelServices=[RWTViewModelServicesImplnew];self.viewModel=[[RWTFlickrSearchViewModelalloc]                    initWithServices:self.viewModelServices];return[[RWTFlickrSearchViewControlleralloc]          initWithViewModel:self.viewModel];}

執行程式,確保程式和之前執行的結果相似.
這並不是最令人興奮的變化,但花點時間來看看新程式碼的”形狀”.
Model層展示了’service’的ViewModel consumes.協議定義了這個服務介面,提供鬆耦合.
你可以用這個假設的服務實現用於單元測試.應用現在已經有了正確地Model-View-ViewModel結構.來小結下:

  1. Model提供應用業務邏輯實現的服務.在本應用中,它提供了Flickr搜尋的服務.
  2. ViewModel層提供了應用的檢視狀態.它提供了使用者互動以及來自檢視變化後Model層的事件.
  3. View層非常瘦,提供了ViewModel狀態變化後在檢視層的展示.
  4. Flickr搜尋

本章節,你將編寫一個真實的Flickr搜尋實現,事情開始變得令人興奮了呢;]
第一步是建立一個搜尋結果的model.
在Model group裡新增一個名為RWTFlickrPhoto的NSObject的子類,在接口裡新增三個屬性:

Objective-C
1234567 @interface RWTFlickrPhoto : NSObject@property(strong,nonatomic)NSString*title;@property(strong,nonatomic)NSURL*url;@property(strong,nonatomic)NSString*identifier;@end

這個Model物件相當於Flickr搜尋API所返回的單張圖片.
在RWTFlickrPhoto.m新增如下方法:

Objective-C