前言
看了前几篇关于RxSwift主要概念的文章,会对RxSwift有个大致的了解,这篇文章会详细讲述如何使用RxSwift,主要的语法,仔细地看完这篇文章就可以用RxSwift开始写app了。
相关文章链接:
正文
Observables(也可以理解成Sequences)
基础
理解观察者模式(observer pattern)和普通序列(sequence)本质上是等价的是理解整个Rx思想很重要的一步。
每一个Observable
序列其实都只是一个序列而已。Observable
相比Swift自带的SequenceType
优势在于,它可以异步地接收元素或者对象。这是整个RxSwift的核心,整个文档讲的内容都是关于这个核心思想的延伸。
-
Observable(ObservableType)
等价于SequenceType
-
ObservableType.subscribe
方法等价于SequenceType.generate
方法 - Observer(也就是回调函数)需要传递给
ObservableType.subscribe
方法来获取序列里的元素,而不是对序列产生器直接调用next()
方法
序列是一个简单熟悉的概念,很容易对它可视化。
人类是一种视觉皮层很大的生物。如果把一个概念可视化,会比较容易地对它进行分析和理解。
我们对于Rx的认知,可以从模拟每一个Rx操作符内部的单个事件的状态提升到对整个序列的整体的操作。
如果我们不用Rx而去使用普通的异步系统,那我们的代码可能会充满了各种状态机和短暂的状态,我们需要模拟这些所有的状态,而不能把它们从一个更高的层面抽象出来。
列表和序列可能是很多数学家和程序员学习到的第一个概念。
这是一个数字序列:
--1--2--3--4--5--6--| // 正常地中断
字符序列
--a--b--a--a--a---d---X // 被一个error中断
一些序列是有限的,一些序列式无限的,想一个按按钮操作序列
---tap-tap-------tap--->
上面这些示意图称为弹子图(marble diagram),你可以在这个网站上看到很多类似的示意图 rxmarbles.com。
如果你你想把序列语法写成一个正则表达式,那看起来可能是像这样子的:
next (error | completed)?*
这个正则表达式的意思就是:
- 序列有大于等于0个元素
- 一旦接收到
error
或者completed
事件,序列就不会在产生其他元素
Rx中的序列可以描述成是一种推送接口(push interface)(可以理解成回调函数)
enum Event<Element> {
case next(Element) // 序列的下一个元素
case error(Swift.Error) // 序列由error中断
case completed // 序列成功地
}
class Observable<Element> {
func subscribe(_ observer: Observer<Element>) -> Disposable
}
protocol ObserverType {
func on(_ event: Event<Element>)
}
但一个序列发送 completed
或者 error
事件时,所有内部用来计算这个事件的资源都会被释放
如果想要让序列不再产生新的元素并且立即释放所有资源,可以对返回的订阅(subscription)调用dispose
如果一个序列是有限序列,不调用dispose
或者 addDisposableTo(disposeBag)
不会引发永久的内存泄漏。 但是直到这个序列完成或者被error中断之前,这些资源都会处于使用状态。
如果那个序列因为某种原因一直不中断,资源将会一直处于占用状态,除非手动调用 dispose
方法,使用disposeBag
, takeUntil
或者其他的一些方法,也可以自动释放序列占用的资源。
使用 dispose bags 或者 takeUntil
操作符是比较好的清理资源的的方法。我们推荐在开发产品过程中使用这两种方法即使那些序列是有限序列。
清理(Disposing)
另外还有一个可以中断序列的办法。但我们使用完一个序列之后我们想要释放所有的资源去计算以后的元素,我们可以对订阅(subscription)调用dispose
。
这里是一个关于 interval
操作符的例子:
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
.subscribe { event in
print(event)
}
NSThread.sleep(forTimeInterval: 2.0)
subscription.dispose()
This will print:
0
1
2
3
4
5
这仅仅是一个示例,通常情况下你不用调用dispose
,手动调用这个方法是一种不太好的代码习惯。更好的办法是使用 DisposeBag
, takeUntil
操作符或者一些其他机制。
dispose
方法被执行之后这段代码还会继续打印吗?答案是:不一定。
如果
scheduler
是一个 serial scheduler (比如说MainScheduler
) 并且dispose
在同一个 serial scheduler 被调用,答案是 不会。否则答案是会的。
你可以在这里了解更多有关 Scheduler 的信息。
你有两个处理在同时运行。
- 一个在产生元素
- 另一个在清理订阅
当这些运算在不同的scheduler里进行的时候,“清理之后会继续打印吗?”这种问题是没有意义的。
再举几个类似的例子 (observeOn
在这里有比较详细的解释)
如果我们有这样一段代码:
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
.observeOn(MainScheduler.instance)
.subscribe { event in
print(event)
}
// ....
subscription.dispose() // 在主线程调用这个函数
在dispose
调用返回之后,就肯定不会再打印任何东西了
同样的,在下面这个例子中:
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
.observeOn(serialScheduler)
.subscribe { event in
print(event)
}
// ...
subscription.dispose() // 在同样的`serialScheduler`执行
在 dispose
调用返回之后,也不会再打印任何东西
清理袋(Dispose Bags)
Dispose bags 在Rx中的作用跟ARC类似。
当一个 DisposeBag
被释放时,它会对内部每一个可以被清理的元素调用 dispose
。
DisposeBag
没有 dispose
方法,所以不允许显示地调用这个方法。如果你需要立即的清理资源,可以创建一个新的DisposeBag
。
self.disposeBag = DisposeBag()
这样会清理旧的引用并且清理资源。
如果仍然需要手动调用清理,可以用 CompositeDisposable
. 它有着跟DisposeBag
一样的功能,但是一旦dispose
方法被调用,这个方法将会立刻清理新加入的待清理的东西
Take until
另一种自动清理订阅的办法是用 takeUntil
操作符。
sequence
.takeUntil(self.rx.deallocated)
.subscribe {
print($0)
}
隐性的 Observable
规定
有一些所有序列生产者 (Observable
s) 必须遵守的规定。
在哪一个线程产生元素并不重要,但如果一个元素产生并发送给observer observer.on(.next(nextElement))
,直到observer.on
方法完成前一个操作才能发送下一个元素。
在 .next
事件没有完成的情况下,发送者也不能发送 .completed
或者 .error
事件。
举个例子来说:
someObservable
.subscribe { (e: Event<Element>) in
print("Event processing started")
// processing
print("Event processing ended")
}
将会一直打印:
Event processing started
Event processing ended
Event processing started
Event processing ended
Event processing started
Event processing ended
而不会打印:
Event processing started
Event processing started
Event processing ended
Event processing ended
创造你自己的 Observable
(也被称作 Observable Sequence)
理解 observables 有非常重要的一点。
当一个observable刚刚被创建出来的时候,不做任何工作,因为observable已经被创造了。
Observable
会通过很多办法产生元素。其中的一些会引起副作用,一些会发生在已经运行的程序里,想鼠标点击事件等等
但是如果你点用了一个方法并且返回了一个 Observable
, 没有序列会产生,也没有副作用会产生。Observable
仅仅是一个定义,定义序列怎么生产的,要用什么参数去产生元素。序列真正开始产生是在 subscribe
方法被调用之后。
比如:我们有一个方法有这样的原型:
func searchWikipedia(searchTerm: String) -> Observable<Results> {}
let searchForMe = searchWikipedia("me")
// 此时不发送任何URL请求,不作任何工作
let cancel = searchForMe
// 发送URL请求,开始产生序列
.subscribe(onNext: { results in
print(results)
})
创建 Observable
序列有很多种方法。最简单的方法可能是用create
函数.
我们可以写一个函数,这个函数创建一个返回一个被订阅元素的序列。 这个函数我们成为 'just' 函数。
这段代码是真实的代码
func myJust<E>(element: E) -> Observable<E> {
return Observable.create { observer in
observer.on(.next(element))
observer.on(.completed)
return Disposables.create()
}
}
myJust(0)
.subscribe(onNext: { n in
print(n)
})
会打印:
0
那么什么是 create
函数呢?
create
函数仅仅是一个用起来比较方便的函数,它能够简单地用Swift的闭包实现订阅 subscribe
方法。'subscribe' 方法接受一个 observer
参数,返回可清理的订阅对象。
用这种方式实现的序列是同步的,这种序列产生元素并且中断之后 subscribe
方法才会开始调用然后返回可清理的订阅对象。因为这个原因,返回怎么样的可清理对象并不重要,产生元素的过程不能被打断。
对于同步序列来说,最常见的返回的disposable就是 NopDisposable
的signleton实例。
让我们创建一个可以返回数组中元素的 observable。
这段代码是真实的代码
func myFrom<E>(sequence: [E]) -> Observable<E> {
return Observable.create { observer in
for element in sequence {
observer.on(.next(element))
}
observer.on(.completed)
return Disposables.create()
}
}
let stringCounter = myFrom(["first", "second"])
print("Started ----")
// first time
stringCounter
.subscribe(onNext: { n in
print(n)
})
print("----")
// again
stringCounter
.subscribe(onNext: { n in
print(n)
})
print("Ended ----")
会打印:
Started ----
first
second
----
first
second
Ended ----
创建一个可以工作的 Observable
事情变得越来越有趣了,让我们写一个之前的例子中用到的 interval
操作符。
这是用 dispatch queue 实现的等价的代码
func myInterval(interval: NSTimeInterval) -> Observable<Int> {
return Observable.create { observer in
print("Subscribed")
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
var next = 0
dispatch_source_set_timer(timer, 0, UInt64(interval * Double(NSEC_PER_SEC)), 0)
let cancel = Disposables.create {
print("Disposed")
dispatch_source_cancel(timer)
}
dispatch_source_set_event_handler(timer, {
if cancel.isDisposed {
return
}
observer.on(.next(next))
next += 1
})
dispatch_resume(timer)
return cancel
}
}
let counter = myInterval(0.1)
print("Started ----")
let subscription = counter
.subscribe(onNext: { n in
print(n)
})
NSThread.sleep(forTimeInterval: 0.5)
subscription.dispose()
print("Ended ----")
会打印:
Started ----
Subscribed
0
1
2
3
4
Disposed
Ended ----
如果你写成:
let counter = myInterval(0.1)
print("Started ----")
let subscription1 = counter
.subscribe(onNext: { n in
print("First \\(n)")
})
let subscription2 = counter
.subscribe(onNext: { n in
print("Second \\(n)")
})
NSThread.sleep(forTimeInterval: 0.5)
subscription1.dispose()
NSThread.sleep(forTimeInterval: 0.5)
subscription2.dispose()
print("Ended ----")
会打印:
Started ----
Subscribed
Subscribed
First 0
Second 0
First 1
Second 1
First 2
Second 2
First 3
Second 3
First 4
Second 4
Disposed
Second 5
Second 6
Second 7
Second 8
Second 9
Disposed
Ended ----
每一个订阅者只订阅跟它自己有关的元素序列。操作符默认是没有状态的。没有状态的操作符远远比有状态的操作符要多。
分享订阅和 shareReplay
操作符
但是如果你想要多个observers分享来自同一个订阅的事件或者元素呢?
有两件事需要去定义。
- 怎么处理在新的订阅者开始观察序列之前已经接收到的元素(只重播最近的一个元素?重播之前所有的元素?还是重播之前n个元素?)
- 怎么决定什么时候去开启共享的订阅(refCount, 手动开启,或者别其他的算法)
常用的选择是 replay(1).refCount()
的结合,也就是我们之前提到的 shareReplay()
.
let counter = myInterval(0.1)
.shareReplay(1)
print("Started ----")
let subscription1 = counter
.subscribe(onNext: { n in
print("First \\(n)")
})
let subscription2 = counter
.subscribe(onNext: { n in
print("Second \\(n)")
})
NSThread.sleepForTimeInterval(0.5)
subscription1.dispose()
NSThread.sleepForTimeInterval(0.5)
subscription2.dispose()
print("Ended ----")
会打印:
Started ----
Subscribed
First 0
Second 0
First 1
Second 1
First 2
Second 2
First 3
Second 3
First 4
Second 4
First 5
Second 5
Second 6
Second 7
Second 8
Second 9
Disposed
Ended ----
注意到与之前那个例子结果的变化,现在仅有一个 Subscribed
和一个 Disposed
事件。
URL observables 的行为也是相同的。
这就是HTTP请求在Rx里被封装的过程,模式和 interval
操作符非常像。
extension Reactive where Base: NSURLSession {
public func response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> {
return Observable.create { observer in
let task = self.dataTaskWithRequest(request) { (data, response, error) in
guard let response = response, data = data else {
observer.on(.error(error ?? RxCocoaURLError.Unknown))
return
}
guard let httpResponse = response as? NSHTTPURLResponse else {
observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
return
}
observer.on(.next(data, httpResponse))
observer.on(.completed)
}
task.resume()
return Disposables.create {
task.cancel()
}
}
}
}
操作符
RxSwift实现了很多不同的操作符。完成的列表可以在这里找到。
所有操作符的弹子示意图可以在 ReactiveX找到。
几乎所有操作符的使用演示都可以在 Playgrounds 找到。
想要使用 playgrounds,请先打开 Rx.xcworkspace
, build RxSwift-OSX
scheme 然后在 Rx.xcworkspace
的树状视图里打开 playgrounds。
如果你需要一个操作符,但不知道用哪个,你可以用这张表帮你找到合适的操作符 decision tree of operators。
Supported RxSwift operators 同样也把函数以功能分类,可以帮助你使用。
自定义操作符
可以通过两种方式创建自定义的操作符。
简单的方法
所有内部的代码使用了操作符的高度优化版本,所以这些代码不是很好的学习材料,我们推荐使用标准操作符。
幸运的是有一种更简单地创建操作符的办法。创建操作符本质上就是创建 observable 的过程,前几个段落中我们已经描述了如何创建的过程。
让我们看一下一个没有优化的 map
操作符是怎么实现的。
extension ObservableType {
func myMap<R>(transform: E -> R) -> Observable<R> {
return Observable.create { observer in
let subscription = self.subscribe { e in
switch e {
case .next(let value):
let result = transform(value)
observer.on(.next(result))
case .error(let error):
observer.on(.error(error))
case .completed:
observer.on(.completed)
}
}
return subscription
}
}
}
现在你可以用你自己的 map
:
let subscription = myInterval(0.1)
.myMap { e in
return "This is simply \\(e)"
}
.subscribe(onNext: { n in
print(n)
})
会打印:
Subscribed
This is simply 0
This is simply 1
This is simply 2
This is simply 3
This is simply 4
This is simply 5
This is simply 6
This is simply 7
This is simply 8
...
实际的运用
如果你觉得处理自定义操作符中的一些情况有些很难处理的情况,你可以选择先不用Rx monad
先回到命令式语言完成你想要完成的工作,然后再用 Subject
把结果返回到 Rx。
这不是非常好的写代码的方法,不应该经常使用,但你可以这样写。
let magicBeings: Observable<MagicBeing> = summonFromMiddleEarth()
magicBeings
.subscribe(onNext: { being in // exit the Rx monad
self.doSomeStateMagic(being)
})
.addDisposableTo(disposeBag)
//
// Mess
//
let kitten = globalParty( // calculate something in messy world
being,
UIApplication.delegate.dataSomething.attendees
)
kittens.on(.next(kitten)) // send result back to rx
//
// Another mess
//
let kittens = Variable(firstKitten) // again back in Rx monad
kittens.asObservable()
.map { kitten in
return kitten.purr()
}
// ....
当你这样写代码的时候,别人可能在其他地方写了这样的代码,所以尽量不要用上面的方式写代码。
kittens
.subscribe(onNext: { kitten in
// so something with kitten
})
.addDisposableTo(disposeBag)
Playgrounds
如果你不确定有一些操作符到底怎么工作,playgrounds 里面包含了几乎所有操作符的演示例子,展示每个操作符的行为。
用 playground 的时候先打开 Rx.xcworkspace, build RxSwift-OSX scheme 然后在Rx.xcworkspace 树状视图中打开 playground、
如果要看每个例子的结果,可以打开 Assistant Editor
。点击 View > Assistant Editor > Show Assistant Editor
可以打开 Assistant Editor
。
Error 处理
有两种error机制:
observables中的异步error处理机制
Error处理非常直接。如果序列被一个error中断,那么所有与之相关的序列都会被这个error中断。这是常见的短路逻辑。
你可以用 catch
操作符从 observable 的失败中恢复。有各种各样的重载可以让你恢复很多序列的细节。
操作符可以在序列出现 error 的情况下重新尝试原来的操作。
Debugging 编译错误
当你写好了优雅的 RxSwift/RxCocoa 代码,你可能会强烈依赖编译器对于 Observable
的推断类型。这也是为什么有时候 Swift 很好用,而有时候却让人沮丧的原因。
images = word
.filter { $0.containsString("important") }
.flatMap { word in
return self.api.loadFlickrFeed("karate")
.catchError { error in
return just(JSON(1))
}
}
如果编译器报告这个表达式有问题,我会建议先添加返回值的类型。
images = word
.filter { s -> Bool in s.containsString("important") }
.flatMap { word -> Observable<JSON> in
return self.api.loadFlickrFeed("karate")
.catchError { error -> Observable<JSON> in
return just(JSON(1))
}
}
如果任然不工作,你可以继续添加更多类型知道你确定了错误是什么。
images = word
.filter { (s: String) -> Bool in s.containsString("important") }
.flatMap { (word: String) -> Observable<JSON> in
return self.api.loadFlickrFeed("karate")
.catchError { (error: NSError) -> Observable<JSON> in
return just(JSON(1))
}
}
我建议先添加闭包的参数和返回值的类型。
通常来说当你解决了错误之后,你可以移除类型的注解让你的代码变得更干净。
Debugging
用 debugger 很有用,但用 debug
操作符会更加高效。debug
操作符会把所有的事件打印到标准输出,你也可以给这些事件加上标注。
debug
的作用像探测器一样。这里是一个例子:
let subscription = myInterval(0.1)
.debug("my probe")
.map { e in
return "This is simply \\(e)"
}
.subscribe(onNext: { n in
print(n)
})
NSThread.sleepForTimeInterval(0.5)
subscription.dispose()
会打印:
[my probe] subscribed
Subscribed
[my probe] -> Event next(Box(0))
This is simply 0
[my probe] -> Event next(Box(1))
This is simply 1
[my probe] -> Event next(Box(2))
This is simply 2
[my probe] -> Event next(Box(3))
This is simply 3
[my probe] -> Event next(Box(4))
This is simply 4
[my probe] dispose
Disposed
你可以很容易地实现你自己的 debug
操作符。
extension ObservableType {
public func myDebug(identifier: String) -> Observable<Self.E> {
return Observable.create { observer in
print("subscribed \\(identifier)")
let subscription = self.subscribe { e in
print("event \\(identifier) \\(e)")
switch e {
case .next(let value):
observer.on(.next(value))
case .error(let error):
observer.on(.error(error))
case .completed:
observer.on(.completed)
}
}
return Disposables.create {
print("disposing \\(identifier)")
subscription.dispose()
}
}
}
}
Debugging 内存泄漏
在debug模式里,Rx用全局变量 resourceCount
追踪所有被占用的资源。
如果你想检测资源泄漏,最简单的方法就是 RxSwift.resourceCount
打印出来。
/* add somewhere in
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
*/
_ = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.subscribe(onNext: { _ in
print("Resource count \\(RxSwift.resourceCount)")
})
检测内存泄漏最有效的方法就是:
- 导航到你想要检测的页面并且使用那个页面的功能
- 导航返回
- 观察最初的资源计数
- 第二次导航到那个页面并且使用
- 导航返回
- 观察最终的资源计数
如果这两次的计数不一样的话,那很有可能存在内存泄漏。
这种两次导航的方法能有效地工作是因为第一次导航会强行加载 lazy resources。
变量
Variable
代表了一些 observable 的状态。 Variable
必须包含一个值,因为 Variable
的初始化需要一个初始值。
Variable 封装了 Subject
。更具体一点,Variable 就是一个 BehaviorSubject
。不同于 BehaviorSubject
的一点是,它仅仅公开 value
interface,所以 variable 不能中断或者失败。
Variable 会在被订阅之后立即广播它现在的值。
在 variable 被释放之后,它会完成从 .asObservable()
返回的 observable 序列。
let variable = Variable(0)
print("Before first subscription ---")
_ = variable.asObservable()
.subscribe(onNext: { n in
print("First \\(n)")
}, onCompleted: {
print("Completed 1")
})
print("Before send 1")
variable.value = 1
print("Before second subscription ---")
_ = variable.asObservable()
.subscribe(onNext: { n in
print("Second \\(n)")
}, onCompleted: {
print("Completed 2")
})
variable.value = 2
print("End ---")
会打印:
Before first subscription ---
First 0
Before send 1
First 1
Before second subscription ---
Second 1
First 2
Second 2
End ---
Completed 1
Completed 2
KVO
KVO 一种 Objective-C 机制。那意味着 KVO 没有一种类型安全机制,这个项目想要去解决其中的一些问题。
在这个库中有两种内置的支持 KVO 的方法。
// KVO
extension Reactive where Base: NSObject {
public func observe<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions, retainSelf: Bool = true) -> Observable<E?> {}
}
#if !DISABLE_SWIZZLING
// KVO
extension Reactive where Base: NSObject {
public func observeWeakly<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions) -> Observable<E?> {}
}
#endif
监测 UIView
的 frame 例子:
警告:UIKit 不兼容 KVO, 但这样的代码可以工作。
view
.rx.observe(CGRect.self, "frame")
.subscribe(onNext: { frame in
...
})
或者
view
.rx.observeWeakly(CGRect.self, "frame")
.subscribe(onNext: { frame in
...
})
rx.observe
rx.observe
更加高效因为它是一个 KVO 机制的简单封装,但它运用的场合比较少。
- 它可以用来监测由
self
或者父类开始的路径 (retainSelf = false
) - 它可以用来监测有子类开始的路径 (
retainSelf = true
) - the paths have to consist only of
strong
properties, otherwise you are risking crashing the system by not unregistering KVO observer before dealloc. 路径只能包括strong
属性,否则就会有系统崩溃的风险,因为可能会在释放之前仍然注册着 KVO observer。
例如:
self.rx.observe(CGRect.self, "view.frame", retainSelf: false)
rx.observeWeakly
rx.observeWeakly
比 rx.observe
要慢一点因为它不得不处理对象的释放防止弱引用。
它可以用在所有 rx.observe
可以用的地方。
- 因为它不会保留被监测的目标,它能够去监测任意的对象,即使在不知道所有权关系的情况下。
- 它可以用来监测
weak
属性。
比如:
someSuspiciousViewController.rx.observeWeakly(Bool.self, "behavingOk")
监测结构体
KVO 是一个 Objective-C 机制所有它很依赖 NSValue
。
RxCocoa 内建支持监测 CGRect
, CGSize
和 CGPoint
结构体.
当监测其他结构体的时候,必须手动从 NSValue
中提取结构体。
这里 是一些展示如何通过实现 KVORepresentable
协议来 扩展 KVO 监测机制和 用rx.observe
方法监测其他结构体的例子。
关于 UI 层的建议
当绑定 UIKit 控件的时候,你的 Observable
需要在UI 层满足一些要求。
线程
Observable
需要在 MainScheduler
(主线程 UIThread)发送元素。这其实也是 UIKit/Cocoa 的要求。
让你的API在 MainScheduler
主线程返回结果始终是一个好主意。以防你想要从其他线程中绑定UI,当你在 Debug的时候, build RxCocoa 会抛出一个异常来提醒你。
为了解决这个问题,你需要加入 observeOn(MainScheduler.instance)
。
NSURLSession extensions 默认不在 MainScheduler
返回结果。
Errors
你不能把失败事件绑定到UIKit控件上因为那是没有被定义过的行为。
如果你不知道 Observable
是否会失败,你可以用 catchErrorJustReturn(valueThatIsReturnedWhenErrorHappens)
来确保不会失败,但是在error发生之后,这个序列仍然会成功完成。。
如果想要这个序列继续产生元素,就需要用 retry
操作符。
分享订阅
你常常会想要在UI 层分享订阅。你不想要调用不同的 HTTP 请求来绑定相同的数据到不同的UI 元素。
如果你有下面这段代码:
let searchResults = searchText
.throttle(0.3, $.mainScheduler)
.distinctUntilChanged
.flatMapLatest { query in
API.getSearchResults(query)
.retry(3)
.startWith([]) // clears results on new search term
.catchErrorJustReturn([])
}
.shareReplay(1) // <- notice the `shareReplay` operator
你想要在搜索结果被计算出来的时候立刻分享搜索结果。这就是 shareReplay
的意思。
在UI 层把 shareReplay
加到操作符链的结尾处通常是个很好的习惯,因为你会想要分享计算出来的结果。你不会想要为了得到相同的搜索数据而一次又一次地调用 HTTP 请求,然后绑定到UI 元素上。
看一下 Driver
单元,这个单元被设计来透明地封装 shareReply
的调用,确保元素在主线程被监测,并且没有 error 会被绑定到 UI。
发送 HTTP 请求
HTTP 请求是很多人第一件想做的事情。
首先你需要创建 NSURLRequest
对象,它能代表你想要完成的工作。
Request 决定了这是一个 GET 请求还是 POST 请求, request body的内容, query 参数 ...
我们来演示怎么创建一个简单的 GET 请求:
let request = NSURLRequest(URL: NSURL(string: "http://en.wikipedia.org/w/api.php?action=parse&page=Pizza&format=json")!)
如果你想要执行请求并且与其他 observable 组合工作,你需要学习下面的代码。
let responseJSON = NSURLSession.sharedSession().rx.JSON(request)
// 这个时候还没有触发请求
// `responseJSON` 只是一种如果进行请求的描述。
let cancelRequest = responseJSON
// this will fire the request
.subscribe(onNext: { json in
print(json)
})
NSThread.sleep(forTimeInterval: 3.0)
// 如果你想在3秒钟后取消这个请求,就调用 dispose 方法
cancelRequest.dispose()
**NSURLSession 扩展默认不在 MainScheduler
主线程返回结果。 **
如果你想要在更底层获得 reponse,你可以用:
NSURLSession.shared.rx.response(myNSURLRequest)
.debug("my request") // this will print out information to console
.flatMap { (data: NSData!, response: NSURLResponse!) -> Observable<String> in
if let response = response as? NSHTTPURLResponse {
if 200 ..< 300 ~= response.statusCode {
return just(transform(data))
}
else {
return Observable.error(yourNSError)
}
}
else {
rxFatalError("response = nil")
return Observable.error(yourNSError)
}
}
.subscribe { event in
print(event) // 如果 error 发生,将会在控制台打印出 error
}
记录 HTTP 通信信息
在debug模式中,RxCocoa 会记录所有 HTTP 的请求并打印到控制台。如果你想改变这种设置,请设置 Logging.URLRequests
滤波器。
// 读你自己的配置文件
public struct Logging {
public typealias LogURLRequest = (NSURLRequest) -> Bool
public static var URLRequests: LogURLRequest = { _ in
#if DEBUG
return true
#else
return false
#endif
}
}
RxDataSources
在Rx中,为 UITableView
和 UICollectionView
实现的 datasource类
RxDataSources 的项目可以在 这里找到。
展示如何使用 RxDataSources 的项目RxExample。