我们先来张图:

在 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.

1
2
self.searchController.searchResultsUpdater = self;
self.searchController.delegate = self;

然后去委托的类里面实现相关的方法

2 3 4 5 6 7 8 9 10 11 12
#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];
}
12
#pragma mark - UISearchControllerDelegate

- (void)willPresentSearchController:(UISearchController *)searchController
{
    self.searching = YES;
}

- (void)willDismissSearchController:(NO;
    [self.tableView reloadData];
}

普通情况下我们就是这样来使用 Delegate 的。

平淡无奇。下面我们来用 RACSignal 的实现方法

UISearchController Delegate RACSignal 实现

第一个要明确的是:我们要做什么。

常规模式

根据常规代码来看:

  1. 我们需要在每次输入词变化的时候进行搜索
  2. 需要在进入和退出搜索的时候知道当前状态

1对应的就是实现- (void)updateSearchResultsForSearchController:(UISearchController *)searchController

2对应的是实现- (void)willPresentSearchController:(UISearchController)searchController 和 - (void)willDismissSearchController:(UISearchController)searchController。

现在,让我们来转换为 RAC 的思维模式思考问题。

RAC 模式

  1. UI 上需要搜索结果的 NSArray
  2. 搜索结果搜索关键字得来。
  3. 每次修改关键字都应该更新搜索结果

因此我们要想办法吧 UI 上需要的数据和修改关键字这个动作绑定起来。

同理可以很容易想到。我们也需要把当前 UI 是否处于搜索状态跟会改变搜索状态的动作绑定起来。

要怎么绑定呢? 拥有刚刚 RAC 超过 Hello World 实力的我,想到,我需要构建出来两个RACSignal。

然后进行类似:

2
RAC(self,searchResults) = SignalA;
RAC(;

这样的绑定就皆大欢喜了。

主要用到了下面两个 RAC 的方法

rac_signalForSelector:fromProtocol:

这个方法主要是把 protocal 转为一个 Signal 便于使用。值得注意的是这个函数返回的是一个 RACTuple。 这个 RACTuple 包含了 Selector 方法里面所有的参数。这样需要用到的时候主要按照顺序来获取

rac_liftSelector:withSignalsFromArray:

这个方法它的意思是当传入的 Signals 都至少sendNext过一次,接下来只要其中任意一个signal有了新的内容。就会去触发第一个 selector 参数的方法

构造两个 Signal 的代码如下

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// UISearchController+RAC.m

- (RACSignal *)rac_textSignal
{
    self.searchResultsUpdater = self;
    RACSignal *signal = objc_getAssociatedObject(    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(CIATION_RETAIN_NONATOMIC);
    return signal;
}

- (RACSignal *)rac_isActiveSignal
{
    self.delegate = nil) return signal;

    RACSignal *willPresentSearching = [[@selector(willPresentSearchController:) fromProtocol:UISearchControllerDelegate)] mapReplace:@YES];
    RACSignal *willDismissSearching = [[@selector(willDismissSearchController:) fromProtocol:NO];
    signal = [RACSignal merge:@[willPresentSearching,willDismissSearching]];
    objc_setAssociatedObject(return signal;
}

最终绑定代码如下:

7
// RACMasterViewController.m

    RAC(self rac_liftSelector:@selector(search:) withSignalsFromArray:@[self.searchController.rac_textSignal]];
    [self.searchController.rac_textSignal subscribeNext:^(id x) {
        [self.tableView reloadData];
    }];
    RAC(self.searchController rac_isActiveSignal];

这样我们就写完了一个用 RAC 来替代 Delegate (protocol) 的例子

总结

使用 RAC 其实最重要的思维的转变。 这个转变在写代码的时候如果我没有思考的很清楚。 那我写出来就一团乱麻。还是需要多加锻炼 MVVM 的思维。

实例代码已经上传Github

下集预告 用 RACSignal 替代 Block Callbacks。有人会期待么?

资料

  1. limboy大大
  2. sunnyxx的Reactive Cocoa Tutorial系列
  3. Replacing the Objective-C “Delegate Pattern” with ReactiveCocoa
  4. ReactiveCocoa Lessons Learned