ReactiveCocoaEx 4.1.0

ReactiveCocoaEx 4.1.0

测试已测试
Lang语言 Obj-CObjective C
许可证 MIT
发布上次发布2016年5月

Hai Feng Kao维护。



  • Hai Feng Kao

ReactiveCocoa(RAC)是一个受函数式响应式编程启发的Cocoa框架。它提供API用于创建和转换随时间变化的值流

  1. 简介
  2. 示例:在线搜索
  3. Objective-C和Swift
  4. ReactiveCocoa与Rx有何关联?
  5. 入门指南
  6. Playground

如果您已经熟悉函数式响应式编程或对ReactiveCocoa有所了解,请查看文档部分以获取更多关于其工作方式的详细信息。然后,深入了解我们的文档注释以了解更多关于各个API的信息。

如果您有任何问题,请查看我们的GitHub问题Stack Overflow上是否有答案。如果没有,请随时提交问题

兼容性

此文档记录了RAC 4,目标为Swift 2.2.x。有关Swift 1.2的支持,请参阅RAC 3

非常感谢Rheinfabrik慷慨赞助ReactiveCocoa 3的开发!

简介

ReactiveCocoa受到了函数式响应式编程的启发。而不是使用可变的变量在原地替换和修改,RAC提供“事件流”,由SignalSignalProducer类型表示,这些事件流随时间发送值。

事件流统一了Cocoa中所有用于异步和事件处理的常见模式,包括

因为这些不同的机制都可以用相同的方式表示,所以我们很容易声明性地将它们串联和组合起来,从而减少了需要桥接的 spaghetti 代码和数据。

有关ReactiveCocoa中概念的更多信息,请参阅框架概述

示例:在线搜索

假设你有一个文本框,每当用户在其中输入内容时,你想要发起一个网络请求以搜索该查询。

监测文本编辑

首先,您需要使用RAC对UITextField扩展进行监测,以检测文本框的编辑。

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

这会给我们一个发送类型为String值的信号生产者(当前需要进行强制转换才能将此扩展方法从Objective-C桥接过来。)

进行网络请求

对于每一个字符串,我们希望执行一个网络请求。幸运的是,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生产者转换成了包含搜索结果的Array的生产者,这些结果将通过主线程发送(归功于UIScheduler)。

此外,这里的flatMap(.Latest)确保只有一个搜索——最新的——能够运行。如果用户在正在进行的网络请求中输入另一个字符,它将在开始新请求之前被取消。想想看,要手动做这件事需要多少代码!

接收结果

这实际上还没有执行,因为生产者必须启动才能接收结果(这样当结果未使用时可以避免执行工作)。这很简单

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

在这里,我们监听包含我们的结果的Next 事件,并将它们简单地记录到控制台。这很容易做到其他事情,比如更新一个表视图或屏幕上的标签。

处理失败

到目前为止,示例中任何网络错误都会产生一个事件,这将终止事件流。不幸的是,这还意味着将不会尝试未来的查询。

为了解决这个问题,我们需要决定如何处理发生的失败。最快的解决方案可能是记录它们然后忽略它们。

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

通过将失败替换为空事件流,我们实际上能够有效忽略它们。

然而,在放弃之前至少尝试几次可能是更合适的。便捷的是,有一个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())

限制请求

现在,假设你只想在用户停止打字时进行实际搜索,以最小化流量。

ReactiveCocoa有一个可以应用于我们的搜索字符串的声明式throttle操作符。

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

这防止了值在小于0.5秒的时间内被发送,因此用户必须至少停止编辑那么长时间,我们才会使用他们的搜索字符串。

手动执行需要大量的状态,最终读起来也更加困难!在ReactiveCocoa中,我们可以使用单个操作符将时间融入我们的事件流中。

调试事件流

由于其本质,流的堆栈跟踪可能包含成百上千的帧,这通常会使调试变得非常耗时。一种简答的调试方法是向流中注入副作用,如下

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都提供了logEvents操作符,它会自动为您做到这一点

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

有关更多信息和高阶用法,请查看调试技术文档。

Objective-C 和 Swift

尽管ReactiveCocoa最初是一个Objective-C框架,但自版本3.0起,所有主要功能开发都集中在Swift API上。

RAC的Objective-C API和Swift API是完全分离的,但有一个桥接器可以在两者之间进行转换。这主要用于作为旧ReactiveCocoa项目的兼容层,或者使用尚未添加到Swift API中的Cocoa扩展。

尽管Objective-C API将继续存在并获得支持,但不会得到很多改进。有关如何使用此API的更多信息,请咨询我们的遗留文档

我们强烈建议所有新项目都使用Swift API。

ReactiveCocoa如何与Rx相联系?

ReactiveCocoa最初受到了微软的Reactive Extensions (Rx) 库的启发,并受到了很大影响。Rx有许多版本,包括RxSwift,但ReactiveCocoa不是有意进行直接转化的。

在RAC与Rx的不同之处,它通常是

  • 创建一个更简单的API
  • 解决常见的混淆源
  • 更接近Cocoa约定

以下是一些具体差异,以及它们的理由。

命名

在大多数版本的Rx中,时间流被称为Observable,这与.NET中的Enumerable类型相并行。此外,Rx.NET中的大多数操作都借用了LINQ的名称,这些名称类似关系数据库的术语,如SelectWhere

**RAC的首要任务是匹配Swift命名**,使用诸如mapfilter之类的术语。其他命名差异通常是受HaskellElm(“信号”术语的主要来源)的显著更好的替代方案所启发。

信号和信号生产者(《热’和《冷’可观测对象)

Rx中最令人困惑的方面之一是关于“热”,“冷”和“温”可观测对象(事件流)。

简而言之,给定一个像这样仅有的方法或函数声明,在C#中

IObservable<string> Search(string query)

……是无法确定订阅(观察)该IObservable是否会涉及副作用。如果确实涉及副作用,也无法判断每个订阅是否都有副作用,或者只有第一次订阅才有。

这个例子是故意的,但它演示了一个真实的、普遍存在的问题,使得Rx代码(以及3.0之前的ReactiveCocoa代码)一瞥之下变得特别难以理解。

通过区分副作用,ReactiveCocoa 3.0通过不同的SignalSignalProducer类型来解决这个问题。尽管这又意味着要学习另一种类型,但它提高了代码的可读性,并更好地传达了意图。

换句话说,ReactiveCocoa这里的改变是简单,并非容易

类型化错误

当在RactiveCocoa中允许信号信号生产者失败时,必须在类型系统中指定错误类型。例如,Signal<Int, NSError>是一个可能以类型为NSError的错误而失败的有整数值的信号。

更重要的是,RAC允许使用特殊类型NoError,这静态地保证事件流不允许发送失败。这消除了许多由意外的失败事件引起的错误。

在具有类型的Rx系统中,事件流仅指定其值的类型——而不是其错误的类型,所以这样的保证是不可能的。

UI编程

Rx基本上对其使用方式漠不关心。尽管用Rx进行UI编程很常见,但它几乎没有为此特定情况量身定制功能。

RAC从ReactiveUI那里汲取了许多灵感,包括动作的基于。

不同的是,ReactiveUI不幸不能直接更改Rx以使其更友好进行UI编程,但ReactiveCocoa已经为此目的进行了多次改进——即使这意味着进一步偏离Rx。

入门

ReactiveCocoa支持OS X 10.9+iOS 8.0+watchOS 2.0tvOS 9.0

将RAC添加到您的应用程序中

  1. 将RactiveCocoa存储库作为您应用程序存储库的子模块
  2. 在RactiveCocoa文件夹中运行script/bootstrap
  3. ReactiveCocoa.xcodeprojCarthage/Checkouts/Result/Result.xcodeproj拖放到您应用程序的Xcode项目或工作区中。
  4. 在您的应用程序目标设置的“通用”选项卡中,将ReactiveCocoa.frameworkResult.framework添加到“嵌入的二进制文件”部分。
  5. 如果您的应用程序目标完全不包含Swift代码,您还应该将构建设置EMBEDDED_CONTENT_CONTAINS_SWIFT设置为“是”。

请确保将ReactiveCocoa.frameworkResult.framework都添加到“链接框架和库”和“复制框架”构建阶段中。

如果您希望使用 CocoaPods,有一些第三方慷慨贡献的 非官方的 podspec 供您选择。

一旦您设置了项目,请查看 框架概览 以了解 ReactiveCocoa 的概念,以及 基本运算符 以了解一些使用它的入门示例。

Playground

我们还提供了一个非常好的 Playground,让您熟悉 ReactiveCocoa 的运算符。为了开始使用它

  1. 克隆 ReactiveCocoa 仓库。
  2. 在RactiveCocoa文件夹中运行script/bootstrap
  3. 打开 ReactiveCocoa.xcworkspace
  4. 构建 Result-MacReactiveCocoa-Mac 目标。
  5. 打开 ReactiveCocoa.playground
  6. 祝您玩得开心!