RxSwift 上手详解 —— 入门篇(翻译三)

前言


看了前几篇关于RxSwift主要概念的文章,会对RxSwift有个大致的了解,这篇文章会详细讲述如何使用RxSwift,主要的语法,仔细地看完这篇文章就可以用RxSwift开始写app了。

相关文章链接:

  1. RxSwift中的Observable
  2. RxSwift中的Subject

正文



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方法,使用disposeBagtakeUntil 或者其他的一些方法,也可以自动释放序列占用的资源。

使用 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,手动调用这个方法是一种不太好的代码习惯。更好的办法是使用 DisposeBagtakeUntil 操作符或者一些其他机制。

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 规定

有一些所有序列生产者 (Observables) 必须遵守的规定。

在哪一个线程产生元素并不重要,但如果一个元素产生并发送给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.observeWeaklyrx.observe 要慢一点因为它不得不处理对象的释放防止弱引用。

它可以用在所有 rx.observe 可以用的地方。

  • 因为它不会保留被监测的目标,它能够去监测任意的对象,即使在不知道所有权关系的情况下。
  • 它可以用来监测 weak 属性。

比如:

someSuspiciousViewController.rx.observeWeakly(Bool.self, "behavingOk")

监测结构体

KVO 是一个 Objective-C 机制所有它很依赖 NSValue

RxCocoa 内建支持监测 CGRect, CGSizeCGPoint 结构体.

当监测其他结构体的时候,必须手动从 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中,为 UITableViewUICollectionView 实现的 datasource类

RxDataSources 的项目可以在 这里找到。

展示如何使用 RxDataSources 的项目RxExample

原文链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容