ReactiveCocoa

原文地址: ReactiveCocoa

说明:现在RAC的版本已经是4.0了,使用Swift2.0开发的。从3.0开始就已经全面转向Swift,并且跟之前OC版本的相比已经有很大不同了。2.5及以前的OC版本能找到很多的学习资料,包括国内以及国外的,就不多说了。我们这里主要面向4.0的版本,以及Swift2.0。

本篇是翻译RAC4.0的README.md,原文地址在开头已经给出了。你也可以在框架的Tag里面找到其他版本的README.md以及其他的文档,可以看看2.5OC版本和现在的版本有什么不一样。

ReactiveCocoa

ReactiveCocoa(RAC) 是受 Functional Reactive Programming 启发而开发的一个Cocoa框架。它为我们提供了组合和转换流的值的API。

如果你已经熟悉了函数响应式编程或者了解ReactiveCocoa是什么了,你可以查看这些 文档 来了解关于ReactiveCocoa是如何工作的更深入的信息。然后直接进入 文档注释 学习更多关于OC和Swift的API。

兼容性:本篇文档是关于RAC 4的,Swift2.x。Swift1.2请参考 RAC3

介绍

ReactiveCocoa 受 Functional Reactive Programming 启发。比起使用可变变量来替换或者修改值,RAC提供了“事件流”,用SignalSignalProducer类型来表示,随着时间的推移会一直传输值。

事件流统一了Cocoa里面为异步操作以及事件处理的常见的编程模式,包括:

  • 代理方法
  • 回调Block
  • 通知
  • 控制事件
  • Futures and promises
  • KVO

因为这些所有不同的机制可以用同一种方式来表示,能够很容易的通过声明的方式链接组合在一起,能给我们的App带来更少的意大利面似得代码,以及更少的状态。

关于ReactiveCocoa更多的概念,参见 Framework Overview

举例:联网搜索

假设你有一个输入框,不管用户输入什么,你都想执行一个网络请求来搜索输入的内容。

Observing text edits(观察用户的输入)

第一步就是观察用户的输入,我们使用RAC里面UITextField的一个拓展来完成这项工作:

let searchStrings = textField.rac_textSignal()
    .toSignalProducer()
    .map { text in text as! String }

我们得到了一个发送String类型值的一个SignalProducer

Making network requests(进行网络请求)

对于每一次输入的字符串,我们都想执行网络请求。幸运的是,RAC提供的NSURLSession的一个拓展能够完成这项工作:

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())

首先降我们发送String类型值的SignalProducer转换为了一个发送包含搜索结果数组的SignalProducer,都会在主线程上发送。

除此之外,.flatMap(.Latest)会保证只有一次搜索-最新的-会被允许执行。如果正在网络请求的时候用户又进行了输入,在开始新的请求前这个网络请求会被取消。想想你自己实现这些功能需要手敲多少代码。

Receiving the results(接收结果)

其实这些代码并不会执行,因为SignalProducer必须started才能接收到结果(防止做了工作而并没有使用结果)。很简单:

searchResults.startWithNext { results in
    print("Search results: \(results)")
}

这里我们监控了Next事件,包含了我们的请求结果,然后打印到控制台。或者你可以在这里做一些其它的事情,比如更新列表视图或者界面上的标签。

Handling failures(处理失败)

在这个例子中,到目前为止,任何网络请求错误都将会生成Failed事件,将会结束这个事件流。不幸的是,这意味着之后的查询甚至都不会被尝试执行。

为了解决这种情况,我们需要决定当失败发生时要做些什么。最快速的解决方案就是打印到日志,然后忽视它们:

.flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in
    let URLRequest = self.searchRequestWithEscapedQuery(query)

    return NSURLSession.sharedSession()
        .rac_dataWithRequest(URLRequest)
        .flatMapError { error in
            print("Network error occurred: \(error)")
            return SignalProducer.empty
        }
}

通过使用empty事件流来替换失败,就能够忽视它们。

然后,在放弃之前至少尝试几次可能更合理。方便的是,正好有retry操作符。

改进后的searchResults如下:

let searchResults = searchStrings
    .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), 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, URLResponse) -> String in
        let string = String(data: data, encoding: NSUTF8StringEncoding)!
        return self.parseJSONResultsFromString(string)
    }
    .observeOn(UIScheduler())

Throttling requests(请求节流)

现在,你只想在用户停止输入的时候才执行搜索,来减少请求拥堵。

ReactiveCocoa 定义了throttle操作符来实现上面的需求:

let searchStrings = textField.rac_textSignal()
    .toSignalProducer()
    .map { text in text as! String }
    .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler)

当用户停止编辑0.5秒后我们才会使用输入的字符串进行搜索。

如果我们手动的来实现这个需求,需要明显的状态变量,然后代码很难读。使用ReactiveCocoa,我们只需要一个操作符就能把将时间加入到事件流中。

Objective-C and Swift

尽管ReactiveCocoa是以Objective-C框架开始的,3.0版本,所有主要的功能开发都集中到了Swift API上。

RAC的Objective-C API和Swift API是完全分开的,但是有桥接文件是它们可以互相转换。这样能够兼容老的ReactiveCocoa项目,同时也能使用还没添加到Swift API的Cocoa extensions。

Objective-C API将会继续存在一段时间,但是不再接收很多改进了。更多关于使用Objective-C API 的信息,参阅 legacy documentation

强烈推荐所有的新项目使用Swift API。

剩下的内容就不翻译了,不是很重要。最后要说的一点就是3.0版本和之前版本最大的区别就是冷信号和热信号了。之前都是由RACSignal表示的,3.0之后就分开了,由Signal表示热信号,SignalProducer表示冷信号。

坚持原创技术分享,您的支持将鼓励我继续创作!