開篇扯淡
最近又在看 ReactiveCocoa 了(下面用 RAC 來替代 ReactiveCocoa)。雖然依然是 hello world 級別。但是 hello world 也是可以分級別的。這次自我感覺是一個偏向中級的 hello world。
我們先來張圖:
在 RAC 的文檔和一些介紹 RAC 的 Keynote 資料裡面我們可以看到說 RACSignal 可以來替代 Delegate、 Block Callbacks、Target Action、KVO、Notifications。
但是貌似沒有一種 hello world 的方式來進行說明如何替代的。
插嘴:在中文 blog 裡面見過幾個寫 RAC 的比較好哒。一個是limboy大大的幾篇深入淺出令人歎為觀止,李忠大大不但研究透徹了然後還結合自己的實戰經驗寫成很好的文章來分享。 另一個是sunnyxx的Reactive Cocoa Tutorial系列這個系列比較偏向研究 RAC 是如何實現和工作的。
我這個人比較笨,最喜歡寫 hello world。那就找時間一個一個來寫呗。
寫之前 Google 了一下。所以以下內容大量參考:Replacing the Objective-C “Delegate Pattern” with ReactiveCocoa。能看原文就去看看。然後忽略掉以下的 hello world 就好了。
實現功能說明
本來想改成 TableView 的。改著改著感覺 TableView 的話。可能會牽扯到 MVVM 的問題。 才能架構出來一個正確的程序結構。而我只想說明簡單的寫清楚如何替代Delegate。所以相當於一個中文簡化版本的 Replacing the Objective-C “Delegate Pattern” with ReactiveCocoa了。
那就跟他一樣寫搜索把。然後實現過程中發現 iOS 8 用新的 UISearchController 來替代了 UISearchDisplayController 了。
UISearchController Delegate 常規實現
一般來說我們會設置protocol.
self.searchController.searchResultsUpdater = self;
self.searchController.delegate = self;
然後去委托的類裡面實現相關的方法
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
if (searchController.searchBar.text.length > 0) {
self.searchResults = [self search:searchController.searchBar.text];
} else {
self.searchResults = self.searchTexts;
}
[self.tableView reloadData];
}
#pragma mark - UISearchControllerDelegate
- (void)willPresentSearchController:(UISearchController *)searchController
{
self.searching = YES;
}
- (void)willDismissSearchController:(UISearchController *)searchController
{
self.searching = NO;
[self.tableView reloadData];
}
普通情況下我們就是這樣來使用 Delegate 的。
平淡無奇。下面我們來用 RACSignal 的實現方法。
UISearchController Delegate RACSignal 實現
第一個要明確的是:我們要做什麼。
常規模式
根據常規代碼來看:
我們需要在每次輸入詞變化的時候進行搜索。
需要在進入和退出搜索的時候知道當前狀態
1 對應的就是實現- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
2 對應的是實現- (void)willPresentSearchController:(UISearchController )searchController 和 - (void)willDismissSearchController:(UISearchController )searchController。
現在,讓我們來轉換為 RAC 的思維模式思考問題。
RAC 模式
UI 上需要搜索結果的 NSArray
搜索結果由搜索關鍵字得來。
每次修改關鍵字都應該更新搜索結果。
因此我們要想辦法吧 UI 上需要的數據和修改關鍵字這個動作綁定起來。
同理可以很容易想到。我們也需要把當前 UI 是否處於搜索狀態跟會改變搜索狀態的動作綁定起來。
要怎麼綁定呢? 擁有剛剛 RAC 超過 Hello World 實力的我,想到,我需要構建出來兩個RACSignal。
然後進行類似:
RAC(self, searchResults) = SignalA;
RAC(self, searching) = SignalB;
這樣的綁定就皆大歡喜了。
主要用到了下面兩個 RAC 的方法:
rac_signalForSelector:fromProtocol:
這個方法主要是把 protocal 轉為一個 Signal 便於使用。值得注意的是這個函數返回的是一個 RACTuple。 這個 RACTuple 包含了 Selector 方法裡面所有的參數。這樣需要用到的時候主要按照順序來獲取。
rac_liftSelector:withSignalsFromArray:
這個方法它的意思是當傳入的 Signals 都至少sendNext過一次,接下來只要其中任意一個signal有了新的內容。就會去觸發第一個 selector 參數的方法。
構造兩個 Signal 的代碼如下
// UISearchController+RAC.m
- (RACSignal *)rac_textSignal
{
self.searchResultsUpdater = self;
RACSignal *signal = objc_getAssociatedObject(self, _cmd);
if (signal != nil) {
return signal;
}
signal = [[self rac_signalForSelector:@selector(updateSearchResultsForSearchController:) fromProtocol:@protocol(UISearchResultsUpdating)] map:^id(RACTuple *tuple) {
UISearchController *searchController = tuple.first;
return searchController.searchBar.text;
}];
objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return signal;
}
- (RACSignal *)rac_isActiveSignal
{
self.delegate = self;
RACSignal *signal = objc_getAssociatedObject(self, _cmd);
if (signal != nil) return signal;
RACSignal *willPresentSearching = [[self rac_signalForSelector:@selector(willPresentSearchController:) fromProtocol:@protocol(UISearchControllerDelegate)] mapReplace:@YES];
RACSignal *willDismissSearching = [[self rac_signalForSelector:@selector(willDismissSearchController:) fromProtocol:@protocol(UISearchControllerDelegate)] mapReplace:@NO];
signal = [RACSignal merge:@[willPresentSearching, willDismissSearching]];
objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return signal;
}
最終綁定代碼如下:
// RACMasterViewController.m
RAC(self, searchResults) = [self rac_liftSelector:@selector(search:) withSignalsFromArray:@[self.searchController.rac_textSignal]];
[self.searchController.rac_textSignal subscribeNext:^(id x) {
[self.tableView reloadData];
}];
RAC(self, searching) = [self.searchController rac_isActiveSignal];
這樣我們就寫完了一個用 RAC 來替代 Delegate (protocol) 的例子
總結
使用 RAC 其實最重要的思維的轉變。 這個轉變在寫代碼的時候如果我沒有思考的很清楚。 那我寫出來就一團亂麻。還是需要多加鍛煉 MVVM 的思維。
實例代碼已經上傳Github
(本文作者:蕭宸宇)