ReactiveCocoa 是一个Objective-C 框架,受 Functional Reactive Programming的启发。它提供了一系列用来组合和转换值流的API。
如果你早已熟悉了函数响应式编程或者知道ReactiveCocoa的基本前提,看看Documentation这个文件夹里的framework overview等文件更深一步来了解它是怎样在实践中工作的。
介绍
ReactiveCocoa受functional reactive programming的启发。在那些能被替换和修改的地方,RAC提供信号(由RACSignal
代表)来捕获当前和将来的值而不是使用可变的变量。
通过链接,组合,和反馈的信号,软件可以不需要写那些持续观察和更新value的代码。
例如,一个文本框能够根据它的改变被绑定到最后一次的值,而不是使用额外的代码每秒去监控时钟和更新文本框。这点跟KVO很像,不过是使用了block,而非-observeValueForKeyPath:ofObject:change:context:
信号也可以进行异步操作,就像futures and promises。这极大的简化了异步软件中网络连接的代码。
RAC的重大优势之一就是它提供信号(signal
)这种方式来统一的处理所有异步的行为,包括代理方法、block 回调、target-action 机制、通知和KVO。
这里是简单的例子:
1 | // 当self.username改变时,打印新的名字到控制台 |
与KVO 通知不同的是信号能够进行统一的链式操作:
1 | // 只有当名字的开头为"j"时才打印 |
信号也能被用来派生状态。在响应新值中RAC代替观察属性和设置其他的属性,能够在信号和运行周期内传达属性:
1 | // 当self.password 和 self.passwordConfirmation相同时创建一个单向的binding使得self.createEnabled为true |
信号不仅是在KVO上,还能在建立在随着时间而改变的值流上。例如,它们可以代表按钮点击:
1 | // 当按钮被点击时打印信息 |
或者是异步网络操作:
1 | // 连接"Log in"按钮给网络登录 |
信号也可以代表定时器,其他的UI事件,或者别的什么随时间而改变的事件。
在异步操作方面,通过链接和转换信号可以建立更复杂的行为。在一组完整的操作之后更简单的来执行工作:
1 | // 执行2个网络操作,当它们都完成时打印信息到控制台 |
信号可以被链接到顺序执行异步操作,而不是使用一堆block回调。通常这样简单的来使用futures and promises:
1 | // 用户登录,下载缓存信息,获取服务器信息。都完成后将信息打印到控制台 |
RAC甚至可以简单的建立在一个异步操作的结果上:
1 | // 创建一个单向的binding,让 self.imageView.image 来放置下载下来的user的头像 |
这是一些使用RAC的示范操作,但是它并不能说明RAC为什么如此强大。
更多示例代码参见C-41 或 GroceryList,这些是使用ReactiveCocoa编写的iOS APP。在这个文件夹Documentation中可以查到更多的关于RAC的信息。
使用ReactiveCocoa
乍一看ReactiveCocoa是非常抽象的,很难理解该怎样将它应用到具体的问题上。
这有一些示例来展示RAC的优势
处理异步或事件驱动的数据源
许多Cocoa编程的重点是对用户事件的反应或应用状态的变化。处理这些事件的代码很快变得非常复杂的就像意大利面一样,伴随着许多回调函数和状态变量处理顺序的问题。
表面上看起来模式不同,比如UI回调,网络响应和KVO通知,实际上有很多共同之处。RACSignal统一了所有的这些不同的API,使他们可以组合在一起,并以同样的方式操纵。
例如这样的代码:
1 | static void *ObservationContext = &ObservationContext; |
… 可以用RAC这样的表示:
1 | - (void)viewDidLoad { |
链接依赖操作
依赖在网络请求中是常见的,在下一个请求建立之前,需要完成当前对服务器的请求,比如:
1 | [client logInWithSuccess:^{ |
在ReactiveCocoa中可以这样简单的实现:
1 | [[[[client logIn] |
并行独立工作
与独立的数据集合并行工作,然后将它们合并成一个non-trivial函数到Cocoa,并经常涉及大量的同步:
1 | __block NSArray *databaseObjects; |
上面的代码可以用简单的合成信号来清理和优化:
1 | RACSignal *databaseSignal = [[databaseClient |
简化collection转换
高阶函数比如 map
, filter
, fold
/reduce
在Foundation中是非常缺少的,导致循环中的代码像这样:
1 | NSMutableArray *results = [NSMutableArray array]; |
RACSequence允许所有Cocoa collection在统一的和声明的方式下被操作:
1 | RACSequence *results = [[strings.rac_sequence |
后记
以上文章摘译自ReactiveCocoa的Objective-C官方文档ReactiveCocoa Documentation
以上内容介绍了RAC的基本用法,仅限于使用,所以墙裂建议仔细学习下节的参考链接,了解RAC原理及高阶用法。
Reference
- ReactiveCocoa的Objective-C官方文档ReactiveCocoa Documentation
- 雷纯锋的ReactiveCocoa v2.5 源码解析之架构总览
- 吖了个峥的最快让你上手ReactiveCocoa之基础篇 和 最快让你上手ReactiveCocoa之进阶篇
- 李忠的ReactiveCocoa与Functional Reactive Programming
- 唐巧的ReactiveCocoa 讨论会