Max 在 Boston 上学,在 San Francisco 工做,是一名软件工程师及创业者。当他还在高中的时候就在一家创业公司工做了,他很是喜欢使用 iOS、Android 以及 JavaScript 框架来为用户编写软件。不过他偶尔也会抱怨创业、技术、书籍、电子游戏等这些杂七杂八的东西。php
@mbalex99css
这几年有不少人在讨论着关于 Rx 的相关概念。Rx 经过 Observable<Element>
接口来表达计算型泛型抽象 (generic abstraction of computation) 的概念,而 RxSwift 是 Rx 的 Swift 版本。无疑,这个内容很是庞大,因此我打算用一种稍微简单点的介绍方式来说解这个框架。java
若是有人写出了下面这么一段代码,相信确定会有不少人感到很是不爽:react
Alamofire.request(.POST, "login", parameters: ["username": "max", "password": "insanity"]) .responseJSON(completionHandler: { (firedResponse) -> Void in Alamofire.request(.GET, "myUserInfo" + firedResponse.result.value) .responseJSON(completionHandler: { myUserInfoResponse in Alamofire.request(.GET, "friendList" + myUserInfoResponse.result.value) .responseJSON(completionHandler: { friendListResponse in Alamofire.request(.GET, "blockedUsers" + friendListResponse.result.value) .responseJSON(completionHandler: { }) }) }) Alamofire.request(.GET, "myUserAcccount" + firedResponse.result.value) .responseJSON(completionHandler: { }) })
这段代码有什么问题呢?这些都是 Alamofire 网络请求方法。若是你们没用过 Alamofire 的话,能够把它看做是 AFNetworking
框架在执行 HTTP
请求操做。在这段代码中你能够看到各类各样的嵌套闭包代码,每多一层嵌套,代码缩进就会向右移动一层,你知道这段代码是有问题的,由于你没法直观地解释这段代码作了什么。在这段代码中涉及到了很多网络服务,而网络颇有可能会访问失败。咱们须要为此添加错误处理,然而咱们却没法知道该在何到处理这些全部的错误异常。Rx 就应运而生,它能够帮助咱们解决这个问题。ajax
在处理不一样事件的时候,不管如何你都会持有一个包含这些事件的集合。举个例子,咱们有这样一个整数数组 [1, 2, 3, 4, 5, 6]
,若是你喜欢你也能够称之为列表。当我须要执行某些操做的时候,最符合 Swift 风格的操做就是使用 filter
方法了。数据库
[1, 2, 3, 4, 5, 6].filter{ $0 % 2 == 0 }
那么若是我想要给这个数组中的全部元素都乘以 5 而后生成一个新数组呢?json
[1, 2, 3, 4, 5, 6].map{ $0 * 5 }
那么执行加法操做呢?数组
[1, 2, 3, 4, 5, 6].reduce(0, +)
这些操做很是简便。咱们没有使用任何 for 循环方法,咱们也不会持有并保持那些临时的中间数。这看起来就很是像 Scala 或者 Haskel 的操做方式。然而,一个正常的应用程序很难只使用数组就能够完成的了。你们都但愿使用网络、下载图片、网上聊天、添加好友等等。你须要大量使用 IO 操做。IO 意味着你须要让内存与用户交互动做、用户设备、相机、硬盘等等诸如此类的东西进行数据交换。这些操做都是异步的,随时均可能会发生失败,而且会发生不少奇怪的问题。安全
我提出了一个关于 Rx 的权利法案,它规定:ruby
咱们的开发者拥有像管理迭代集合 (iterable collections) 同样管理异步事件的权利。
在 Rx 的世界里,让咱们用 观察者 (Observables)
的概念来代替数组。 观察者
是一个类型安全的事件对象,能够长期对不一样种类的数据值进行写入和读出。RxSwift 目前处于 Beta 3 的版本,安装很是简单。你所须要作的就是导入 RxSwift 便可。
pod 'RxSwift', '~> 2.0.0-beta.3' import RxSwift
建立 观察者
也很容易。最简单的建立方式就是使用 just
,这是一个 RxSwift 内建的函数。你能够将你所想要的变量放到其中,它就会返回一个包含相同类型的 观察者
变量。
just(1) //Observable<Int>
那么若是咱们想要从数组中一个接一个的推出元素并执行相关操做呢?
[1,2,3,4,5,6].toObservable() //Observable<Int>
这会返回一个 Observable<Int>
对象。
若是你在使用相似于上传数据到 S3 或者向本地数据库保存数据之类的 API,你能够这样写:
create { (observer: AnyObserver<AuthResponse>) -> Disposable in return AnonymousDisposable { } }
当你调用 create
的时候会返回一个闭包。这个闭包会给予一个 观察者
参数,这意味着有某个东西正在对其进行观察。目前你能够忽略 AnonymousDisposable
这个东西。在下面两行代码中你将会看到你在何处将 API 代码转换为了一个好用的 观察者
对象。
下面这段代码和 Alamofire 的使用方式相同:
create { (observer: AnyObserver<AuthResponse>) -> Disposable in let request = MyAPI.get(url, ( (result, error) -> { if let err = error { observer.onError(err); } else if let authResponse = result { observer.onNext(authResponse); observer.onComplete(); } }) return AnonymousDisposable { request.cancel() } }
我能够进行日志记录操做,也能够执行一个 GET
请求,随后我能够获得一个带有结果和错误的回调函数。实际上我没法改变这个 API,由于这是由另外一个客户端 SDK 所提供的,可是我能够将其转换为一个 观察者
对象。当存在错误的时候,我会调用observer.onError()
方法。这意味着只要监听了这个对象的代码都会接收到这个错误消息。当你获得可用的服务器回应时,调用 observable.onNext()
方法。接着,若是处理结束的话就调用 onComplete()
。这个时候咱们就到了AnonymousDisposable
这里了。 AnonymousDisposable
是当你想要停止请求的时候被调用的操做。好比说你离开了当前视图控制器或者应用已经再也不须要调用这个请求的时候,就可使用这个方法了。这对视频上传等大文件操做是很是有用的。当你结束全部操做的时候, request.cancel()
能够清除全部的资源。不管是操做完成仍是发生错误,这个方法都会被调用。
监听观察者(8:11)
如今咱们知道如何建立观察者了,那么就来看看如何对其创建监听吧!咱们以数组为例,由于咱们能够在不少对象中调用一个名为 toObservable()
的扩展方法。随后,就能够编写监听函数了:
[1,2,3,4,5,6] .toObservable() .subscribeNext { print($0) }
这看起来跟枚举差很少。Subscribe 监听器事件基于失败请求、下一步事件以及onCompleted
操做,给你提供了各类各样的信息。你能够有选择性的创建相应的监听:
[1,2,3,4,5,6] .toObservable() .subscribe(onNext: { (intValue) -> Void in // 推出一个整数数据 }, onError: { (error) -> Void in // 发生错误! }, onCompleted: { () -> Void in // 没有更多的信号进行处理了 }) { () -> Void in // 咱们要清除这个监听器 }
使用 Rx 的最好例子就是套接字 (socket) 服务了。假设咱们有一个网络套接字服务,它用来监听股票行情,而后显示用户的当前帐户余额 UI 界面。因为股票行情对应了不一样的事件,根据这些事件来决定用户是否能够购买股票。若是帐户余额太低的时候咱们将禁止用户购买,当股票票价在用户的承担范围内的时候容许用户购买。
func rx_canBuy() -> Observable<Bool> { let stockPulse : [Observable<StockPulse>] let accountBalance : Observable<Double> return combineLatest(stockPulse, accountBalance, resultSelector: { (pulse, bal) -> Bool in return pulse.price < bal }) }
combineLatest
意味着当某个事件发生的时候,咱们就将最近的两个事件之间创建关联。Redution 闭包是否会被回调取决于股票票价是否低于余额。若是被回调,这就意味着用户能够购买这只股票。这个操做容许你将两个观察者关联起来,而后列出一个逻辑决定某些操做是否能够进行。这会返回一个 Bool
类型的观察者。
rx_canBuy() .subscribeNext { (canBuy) -> Void in self.buyButton.enabled = canBuy }
使用 subscribe 方法来操做刚刚我建立的那个会返回 Bool
值的 rx_canBuy
方法。而后你就能够根据 canBuy
的返回值来决定 self.buyButton
的行为了。
让咱们来举一个合并的例子。假设我有一个存有我所喜欢的股票的用户界面应用。我经过 Apple、Google 以及 Johnson 来监听股票票价。全部这些股票行情都有不一样的结果。当股票行情发生变化的时候我须要马上知道并更新个人用户界面。
let myFavoriteStocks : [Observable<StockPulse>] myFavoriteStocks.merge() .subscribeNext { (stockPulse) -> Void in print("\(stockPulse.symbol)/ updated to \(stockPulse.price)/") }
这些参数的类型都是相同的 Observable<StockPulse>
类型。我须要知道它们什么时候被触发,我须要作的就是持有一个 观察者
数组。里面存放了我须要进行监听的多个不一样种类的股票行情,我能够在一个输出流中将它们合并而后进行监听。
我使用 Rx 使用了很长时间。很遗憾,我仍然忘记了许许多多的操做,而且须要很是频繁地回顾参考文档。这个网站 rxmarbles.com 将会为咱们展现全部这些操做的理论部分。
借助 RxSwift 还有许多很赞的事情能够作。好比说你有一个视频上传操做,因为这个视频文件太大了,所以你须要在后台中进行。执行这个操做的最好办法就是使用observeOn
进行。
let operationQueue = NSOperationQueue() operationQueue.maxConcurrentOperationCount = 3 operationQueue.qualityOfService = NSQualityOfService.UserInitiated let backgroundWorkScheduler = OperationQueueScheduler(operationQueue: operationQueue) videoUpload .observeOn(backgroundWorkScheduler) .map({ json in return json["videoUrl"].stringValue }) .observeOn(MainScheduler.sharedInstance) .subscribeNext{ url self.urlLabel.text = url }
视频上传操做须要给我当前完成度百分比的信号信息,这样我才能知道这个操做是否完成。可是我并不打算在主线程中执行这个操做,由于我能够在后台执行。当视频上传结束以后,我会获得一个返回的 JSON 数据,它会告知我所上传的 URL 地址这样我就能够将其写入到 UI 标签当中了。由于我是在后台进行监听的,所以这个操做不会在主线程上进行。我须要通知 UI 进行更新,这样才能将信息传递给主线程。所以咱们须要回去执行 observeOn(MainScheduler.SharedInstance)
方法,这个操做将会执行 UI 更新操做。遗憾的是,和 Android 上的 RxJava 框架不一样,在 Swift 中若是你在后台进程中更新 UI 也是能够进行的(但不要这么作)。在 Android 中这么作会发生崩溃,而在 Swift 中则不会发生崩溃。
我展现了一些很酷的东西,经过将事件视为数组,你能够用 RxSwift 让代码更简单、更好用。我知道 MVVM 是一个很是庞大的项目,它能够将视图控制器从一个完整的个体变成一个个关联的组织。RxSwift 有一个类似的名为 RxCocoa 的仓库,能够有效地解决这个问题。基本上来讲,它给全部的 Cocoa 类创建了扩展方法,从而可让 UI 视图能够创建诸如 Rx-Text 或者名字文本框之类的东西。这样你就能够少写一点 subscribeNext 方法,从而在多个不一样的视图点中将值和观察值之间创建关联。
咱们生活在一个有多个平台的世界。Rx 对我来讲,最主要的吸引点就是 Rx 能够忽略每个其余客户端 API 执行 IO 的方法。若是你在使用 Android 或者 JavaScript,你必需要学习如何异步管理这些不一样的 IO 操做事件。Rx 是一个支持多平台的辅助库,你能够在多个热门语言中使用:.NET、JavaScript、Java,这三门是除了 Swift 以外最热门的三个语言了。你可使用相同的操做、步骤和心态来编写代码。在全部的语言当中,Rx 看起来都是十分类似的。咱们以一个日志功能为例,首先是 Swift:
func rx_login(username: String, password: String) -> Observable<Any> { return create({ (observer) -> Disposable in let postBody = [ "username": username, "password": password ] let request = Alamofire.request(.POST, "login", parameters: postBody) .responseJSON(completionHandler: { (firedResponse) -> Void in if let value = firedResponse.result.value { observer.onNext(value) observer.onCompleted() } else if let error = firedResponse.result.error { observer.onError(error) } }) return AnonymousDisposable{ request.cancel() } }) }
rx_login
函数能够返回一个你所想要的观察者对象。下面是 Kotlin 版本:
fun rx_login(username: String, password: String): Observable<JSONObject> { return Observable.create ({ subscriber -> val body = JSONObject() body.put("username", username) body.put("password", password) val listener = Response.Listener<JSONObject>({ response -> subscriber.onNext(response); subscriber.onCompleted() }) val errListener = Response.ErrorListener { err -> subscriber.onError(err) } val request = JsonObjectRequest(Request.Method.POST, "login", listener, errListener); this.requestQueue.add(request) }); }
看起来基本差很少,下面是 TypeScript 版本:
rx_login = (username: string, password: string) : Observable<any> => { return Observable.create(observer => { let body = { username: username, password: password }; let request = $.ajax({ method: 'POST', url: url, data: body, error: (err) => { observer.onError(err); }, success: (data) => { observer.onNext(data); observer.onCompleted(); } }); return () => { request.abort() } }); }
不仔细看这些代码形式还真差很少。你能够对全部这类事件随意编写测试用例。你能够没必要写全部的客户端代码或 UI 代码,由于它们都须要自身平台的支持,可是基于服务的类能够很容易地提取出相同的使用接收方式。几乎相同的原理无处不在。
问:您对 RxSwift 和 ReactiveCocoa 都有什么见解吗?
Max:我使用了三年的 Objective-C。ReactiveCocoa 是我早期的试用目标。当我换用 Swift 进行开发的时候,我安装了 ReactiveCocoa 的早期版本,使用起来让我感到很不愉快。我发现经过 Google 搜索我就能够很快地上手 RxSwift。对我我的而言,RxSwift 和 ReactiveCocoa 我都用。人们总会说这两个框架之间有着这样那样的差异,可是我历来没有听人说过 RxSwift 毁了某人的童年,也没据说过 ReactiveCocoa 让某人妻离子散。没有人曾经跟我谈论过这些差别。所以使用哪个是你的自由。RxSwift 是 ReactiveX Github 仓库下的一个项目,所以若是若是对你来讲阅读代码和学习很轻松的话,那么就直接使用 RxSwift 吧!若是你的公司只是关于 iOS 以及 Android 的话,那么就使用 ReactiveCocoa 就行了,就不要再考虑别的了。可是若是你须要准备三个平台的话,好比说有一个和 JS 应用一块儿使用的电子应用,最好是可以将这个应用放到 Spotify 上面,而后给三个平台分别复制服务而后建立一个监视器。你能够在一夜完成这个任务。
问:关于自动完成功能速度慢的问题您有没有建议或者解决方案呢?
Max:我打字速度是很快的,比自动完成功能要快得多了。我大多数时候只在敲下点的时候才查看自动完成列表。若是你输入的速度过快,那么你极可能就没法获得自动完成提示。这是此时 Xcode 面临的实际问题。我在使用自动完成的时候并无碰到问题,而一般我使用自动完成的方法是 flatMap
、 merge
和 combineLatest
。
问:您提到了跨平台。它能在 Linux 上运行吗?
Max:我之因此提到跨平台的特性,是由于它本质上是一个拥有 API 特性的库。之因此这么说是由于你可使用 Java 或者 TypeScript 来实现辅助库的功能,这个库本质上是独立运行的。
问:我注意到这个框架导入的是 Foundation 框架,我好奇的是,若是要摆脱这个基础库的依赖的话,或者替换这个基础库的时候,会不会很是难?
Max:我不清楚。我会具体问下别人这个问题,以后再来回复你。
问:如何对 RxSwift 进行调试呢?
Max:有一个带有闭包的调试函数的。RxSwift 有一个相似库是专门处理闭包的。这个库不会在正式产品中被使用。你实际使用以后你会发现它的功能真的十分好用。它会自行建立闭包调用,所以你无需对这个异步线程进行管理。所以你能够在主线程上面对堆栈进行跟踪。
问:我在想为何您以为专门挑选几个特殊的例子来介绍 RxSwift 是一个好主意呢?或者说为何不全用上 RxSwift 或者 Rearctive 来建立一个完整的应用程序呢?
Max:不少对 RxSwift 进行贡献的人都说,你应当从一开始就在项目中使用 RxSwift。可是这很不现实,所以我决定有选择性地进行介绍。我不认为在场的绝大多数观众朋友们都可以有立马开始新项目的机会。大家你们不少时候是在处理着各类各样有五年或者六年历史的代码库,所以有选择性地进行介绍,决定是否使用就看你们是否喜欢了。
问:我已经用了好久的 ReactiveCocoa 了,其中一件 ReactiveCocoa 所要作的事就是他们想将全部事件都转为信号进行处理,这样你就能够将他们集中在一块儿。在 Objective-C 中他们使用 Selector 来实现此功能。你是否清楚在 RxSwift 中,它是如何使用 Swift 来处理委托回调方法的呢?
Max:是的,若是你看一下在 RxCocoa 中这一部分代码的话,它们会从新激活 (reactifying) 你的 UITableViewDelegates
和 UICollectionViewDelegates
。它们会建立一个微妙的代理,这样你就能够经过闭包来开始接收事件,而后将其转换为 观察者
,以便建立属于你本身的 观察者
集,接着在委托层中接收信号。若是你查看 RxCocoa 库的话你会发现这个操做作得也是很是完美的。