> 作为一个iOS开发小鱼,一直对RAC的使用垂涎不已,却一直没能深入学习,在项目闲暇的空档自娱自乐做个官方简介的中文翻译(结合google翻译).能力有限,如有谬误,还望指正海涵.以下为正文@H_502_3@
ReactiveCocoa
框架代码地址:[https://github.com/ReactiveCocoa/ReactiveCocoa][1]
ReactiveCocoa(RAC)是一个Cocoa框架,灵感来自于函数响应式编程。它提供了实时地对数据流steams of values变化进行展示和响应的API。@H_502_3@
目录:
如果你已经熟悉了函数响应式编程或基本了解了ReactiveCocoa,可以去文档文件夹里了解它的运行原理等信息。或者,直接进入我们的文档评论区,了解更多的相关API。@H_502_3@
如果您有什么疑问,请先查看该问题在问题讨论区或StackOverflow上是否已经有了相关的讨论.如果没有的话,可随时提交给我们!@H_502_3@
兼容性
这份RAC4文档适用于Swift 2.2.x. 关于Swift 1.2的支持请看RAC3.@H_502_3@
介绍
ReactiveCocoa的灵感来自于函数响应式编程.@H_502_3@
不同于就地即时地替换和修改可变变量的方案,RAC提供了“事件流”方案,通过信号Signal和信号产生器的SignalProducer形式来展示和响应实时的数据值values的变化.@H_502_3@
"事件流"统一了Cocoa里所有的非即时的事件处理模式,包括了:@H_502_3@
block回调 Callback blocks@H_502_3@
控制动作和响应链事件 Control actions and responder chain events@H_502_3@
观察者模式 Key-value observing (KVO)@H_502_3@
Futures and promises (不了解)@H_502_3@
由于所有这些不同的机制可以通过相同的方式展示,容易发现将它们整合链接在一起,可以使代码更精简高效,同时使项目统一性更高.less spaghetti code and state to bridge the gap@H_502_3@
更多的有关ReactiveCocoa概念信息,请参见框架概述。@H_502_3@
实例:在线搜索,即时响应
比方说,你有一个文本输入框,当用户键入字段,你想即时对其进行搜索查询。@H_502_3@
监控文本编辑
第一步是监控文本输入框的字段编辑情况,专门对UITextField使用RAC扩展以实现这个需求:@H_502_3@
let searchStrings = textField.rac_textSignal() .toSignalProducer() .map { text in text as! String }
以上代码给我们提供了一个能够发送字符串类型的值的信号产生器. (从Objective-C桥接扩展方法是相当必要的).@H_502_3@
建立网络请求
我们需要随着字符串改变而同时执行网络请求. 同时,RAC提供的一个叫做NSURLSession的扩展可以满足这个需求:@H_502_3@
let searchResults = searchStrings .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData,NSURLResponse),NSError> in let URLRequest = self.searchRequestWithEscapedQuery(query) return NSURLSession.sharedSession().rac_dataWithRequest(URLRequest) } .map { (data,URLResponse) -> String in let string = String(data: data,encoding: NSUTF8StringEncoding)! return self.parseJSONResultsFromString(string) } .observeOn(UIScheduler())
这个将在主线程上将我们的字符串产生器转换成一个包含搜索结果的数组的产生器.(感谢UIScheduler).
此外,flatMap(.Latest) 确保了只有最后一个搜索操作能被执行.@H_502_3@
如果用户在执行网络请求时候输入了其他的字节,那么该网络请求将在下一个网络请求开始前被取消.@H_502_3@
试想如果要自己写,需要多少代码才能实现这个效果.@H_502_3@
接收结果
这不会马上真正地执行,因为如果要收取搜索结果,那么信号产生器必须先运行(这样可以防止无效运行).这很容易做到:@H_502_3@
searchResults.startWithNext { results in print("Search results: \(results)") }
这样,我们要做的就是等待包含搜索结果的事件,然后将事件里的搜索结果打印到控制台,而这个打印操作可以很简单地用其他操作替代,比如说刷新屏幕上显示的各种视图.@H_502_3@
故障处理
这个例子写到这一步,随便一个网络故障都有可能终止事件流.甚至可能会导致所有未来的查询操作都不会进行.@H_502_3@
对此,我们需要决定当故障发生时应该如何处理.最快的解决办法就是先记录故障,然后忽略他们.@H_502_3@
.flatMap(.Latest) { (query: String) -> SignalProducer<(NSData,NSError> in let URLRequest = self.searchRequestWithEscapedQuery(query) return NSURLSession.sharedSession() .rac_dataWithRequest(URLRequest) .flatMapError { error in print("Network error occurred: \(error)") return SignalProducer.empty } }
我们可以用一个很有效的故障忽略办法,就是:通过用空事件流替换故障.@H_502_3@
但在放弃之前多做几次尝试会更合适一些,而且有一个很方便的重试操作正对应这个需求.@H_502_3@
我们的改进后的搜索结果产生器可以是这样的:@H_502_3@
let searchResults = searchStrings .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData,NSError> in let URLRequest = self.searchRequestWithEscapedQuery(query) return NSURLSession.sharedSession() .rac_dataWithRequest(URLRequest) .retry(2) .flatMapError { error in print("Network error occurred: \(error)") return SignalProducer.empty } } .map { (data,encoding: NSUTF8StringEncoding)! return self.parseJSONResultsFromString(string) } .observeOn(UIScheduler())
减少网络请求的流量消耗
可以通过定期地执行实际的搜索操作,以减少流量.@H_502_3@
ReactiveCocoa里有一个我们能应用于搜索的操作:节流器throttle:@H_502_3@
let searchStrings = textField.rac_textSignal() .toSignalProducer() .map { text in text as! String } .throttle(0.5,onScheduler: QueueScheduler.mainQueueScheduler)
这可以防止程序发送时间间隔少于0.5秒的数据请求.@H_502_3@
如果要自己实现这个效果将会需要签名验证的状态significant state,而且代码也会更加难以阅读! 但通过ReactiveCocoa,我们只需要导入一个时间到我们的事件流里.@H_502_3@
调试事件流
由于其本身的特性,一个流的堆栈可能有大量的构架,其中大多通常可以使调试成为一件令人难搞的事情. 如下的插入附加作用side effect到流是一个比较基础的调试方法:@H_502_3@
let searchString = textField.rac_textSignal() .toSignalProducer() .map { text in text as! String } .throttle(0.5,onScheduler: QueueScheduler.mainQueueScheduler) .on(event: { print ($0) }) // the side effect
而以下操作将打印出这个流的事件,同时不影响这个流的原本行为.信号产生器SignalProducer和信号*Signal都会自动为你运行打印操作:@H_502_3@
let searchString = textField.rac_textSignal() .toSignalProducer() .map { text in text as! String } .throttle(0.5,onScheduler: QueueScheduler.mainQueueScheduler) .logEvents()
更多信息和进阶操作,请查看调试技术文档.@H_502_3@
Objective-C 和 Swift
虽然ReactiveCocoa最开始是一个基于Objective-C的框架,但是从3.0版本开始,所有的主要功能开发都集中在Swift的API上.@H_502_3@
在RAC里,Objective-C的API和Swift的API是完全分开的,但是二者的转换是可以桥接的.这主要是为了兼容老的旧的ReactiveCocoa项目,或者是使用了还没添加到Swift API 的Cocoa扩展的项目.@H_502_3@
目前来说,Objective-C的API将继续存在并且得到支持,但不会得到很多的改进. 更多有关于API使用的信息,请查询我们的详细文档.@H_502_3@
我们强烈建议所有新项目使用Swift API.@H_502_3@
RAC与Rx之间的关系?
未完待续
ReactiveCocoa was originally inspired,and therefore heavily influenced,by Microsoft's Reactive Extensions (Rx) library.There are many ports of Rx,including RxSwift,but ReactiveCocoa is intentionally not a direct port.@H_502_3@
Where RAC differs from Rx,it is usually to:@H_502_3@
Create a simpler API@H_502_3@
Address common sources of confusion@H_502_3@
More closely match Cocoa conventions@H_502_3@
The following are some of the concrete differences,along with their rationales.@H_502_3@