开始使用 RxSwift

102 分钟读完

信号即序列

RxSwift 项目尝试与 ReactiveX.io 项目保持一致,一般的跨平台的文档和指导同样适用于 RxSwift

Basic

观察者模式(Observable<Element> sequence)和普通的 sequence 的等价是 Rx 中最重要的概念。

每个 Observable sequence 就是一个 sequence。Observable 相对于 Swift 的 Sequence 的优势在于可以异步地接收元素。这是 RxSwift 的核心,接下来的文档都是对这个想法的扩展。

  • Observable(ObservableType) 等价于 Sequence
  • ObservableType.subscribe 方法等价于 Sequence.makeIterator
  • Observer (callback) 需要被传递给 ObservableType.subscribe 方法来接受序列元素而不是对返回的 iterator 调用 next()

Sequences 是一个简单熟悉的概念,容易可视化

人类是拥有庞大视觉大脑皮层的生物,可以很容易理解可视化之后的概念

我们可以通过在序列更高级的操作上模拟每个 Rx 操作符内部的事件状态机的方式来提升我们的认知

如果我们在构建异步的系统但是没有使用 Rx,很可能意味着我们的代码有很多的状态机和暂态需要去模拟,而不是以一种抽象的方式

列表和序列大概是数学家和程序员首先要学习的概念之一

下面是一个数字序列:

--1--2--3--4--5--6--| // terminates normally

下面是一个字符序列:

--a--b--a--a--a---d---X // terminates with error

有些序列是有限的,有些序列是无限的,比如按钮的点击事件产生的序列:

---tap-tap-------tap--->

这些被称为弹珠图,更多的弹珠图在http://rxmarbles.com

下面举例说明 Rx 中 sequence 的语法:

next* (error | completed)?

表达的信息如下:

  • Sequences 可以有 0 个或者多个元素
  • 一个 error 或者 completed 事件被接收之后,这个序列就不能产生其他的元素了

Rx 中使用推送接口(回调)来描述序列

enum Event<Element>  {
    case next(Element)      // next element of a sequence
    case error(Swift.Error) // sequence failed with error
    case completed          // sequence terminated successfully
}

class Observable<Element> {
    func subscribe(_ observer: Observer<Element>) -> Disposable
}

protocol ObserverType {
    func on(_ event: Event<Element>)
}

当一个序列发送 completederror 事件的时候,这个序列用来计算元素占用的所有的内部资源将被释放

如果要取消序列元素的产生并且立马释放资源,对返回的订阅调用 dispose 方法

如果一个序列会在有限的时间内结束,不调用 dispose 或者 disposed(by: disposeBag) 不会造成永久的资源泄漏。然而,这些资源会在序列完成之前一直被使用,序列完成的情况包括结束了元素的产生或者返回了错误。

如果一个序列不会自己终止,比如按钮的连续点击,资源会一直被占用直到 dispose 被手动调用,在 disposeBag 中被自动调用,使用了 takeUntil 操作符,或者其它的方式。

使用 DisposeBag 或者 takeUntil 操作符来保证资源被正确清理是鲁棒性比较好的一种方式。我们推荐在生产中使用它们,即使序列会在有限的时间内结束

如果你好奇为什么 Swift.Error 不是范型,你可以在这里找到解释

Dispose

还有另外的方式可以终止被订阅的序列。当我们结束了一个序列想要释放用来计算和传递元素所占用的资源时,可以通过一个订阅调用 dispose

下面是一个使用 interval 操作符的例子:

let scheduler = SerialDispatchQueueScheduler(qos: .default)
let subscription = Observable<Int>.interval(.milliseconds(300), scheduler: scheduler)
    .subscribe { event in
        print(event)
    }

Thread.sleep(forTimeInterval: 2.0)

subscription.dispose()

This will print:

0
1
2
3
4
5

通常来说你不需要手动调用 dispose 方法,这里仅仅是为了说明

手动调用 dispose 通常来说可能会造成坏的代码,可以用更好的方式来清理订阅,比如 DisposeBagtakeUntil 操作符,或者其它的机制。

所以在 dispose 方法调用之后还有有东西打印吗?答案是:看情况

  • 如果 schedulerserial scheduler (如:MainScheduler) 并且 dispose 方法在同一个 serial scheduler 上调用,答案就是 no
  • 否则就是 yes

你可以在这里找到更多关于 scheduler 的信息

简单来说你有两个并行的处理过程:

  • 一个在产生元素
  • 另一个在清理订阅

“在调用 dispose 之后还可以有东西打印吗?”的问题在上面所说的两个处理过程运行在不同的 schedulers 上的时候根本就说不通。

看看其它的例子来验证一下(observeOn这里有解释)

假设我们有如下的代码:

let subscription = Observable<Int>.interval(.milliseconds(300), scheduler: scheduler)
            .observeOn(MainScheduler.instance)
            .subscribe { event in
                print(event)
            }

// ....

subscription.dispose() // called from main thread

dispose 调用之后,不会再有东西打印。这是可以保证的

再看下面的例子:

let subscription = Observable<Int>.interval(.milliseconds(300), scheduler: scheduler)
            .observeOn(serialScheduler)
            .subscribe { event in
                print(event)
            }

// ...

subscription.dispose() // executing on same `serialScheduler`

dispose 调用之后,不会再有东西打印。这是可以保证的

Dispose Bags

Dispose bags 给了 Rx 类似于 ARC 的表现

当一个 DisposeBag 被释放的时候,它会对添加进 DisposeBag 中的每一个 disposable 对象都调用一次 dispose 方法

DisposeBag 没有 dispose 方法,所以不能手动调用 dispose 来清理资源,如果需要立刻清理资源,可以创建一个新的 bag 来替换原来的

  self.disposeBag = DisposeBag()

这样可以清理原来的引用并清理资源

如果还是想要手动地显式清理,可以使用 CompositeDisposable它有我们期望的和 dispose 一样的表现,但是实现的不同是,当调用 dispose 方法时,它会立马清理新加进来的 disposable 对象

Take until

还可以使用 takeUntil 操作符来实现在 dealloc 的时候自动清理订阅

sequence
    .takeUntil(self.rx.deallocated)
    .subscribe {
        print($0)
    }

Implicit Observable guarantees

还有一些另外的保证,所有的序列 producers(Observables)必须遵守

它们在哪个线程生成元素并不重要,但是如果它们生成了一个元素然后把生成的元素发送到观察者 observer.on(.next(nextElement)),它们不能再发送下一个元素知道 observer.on 方法执行结束

并且在 .next 没有结束的时候 Producers 不能发送终止事件 .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

Creating your own Observable (aka observable sequence)

关于 Observable 有一个重要的事情需要理解

当一个 observable 被创建的时候,它并没有执行任何工作

比如说一个网络请求的 observable 被创建,实际并没有发出网络请求,被订阅之后才发起请求

Observable 可以通过很多种方式生成元素,有些会引起副作用,有些会进入现有的运行进程中,比如鼠标的点击事件等。

但是,如果你只是调用一个方法返回了 Observable,并没有执行序列的生成并且也没有副作用。Observable 仅仅定义了序列该如何生成和什么使用什么参数生成。序列的实际生成发生在 subscribe 方法调用之后。

看下面的例子,假设你有一个类似原型的方法:

func searchWikipedia(searchTerm: String) -> Observable<Results> {}
let searchForMe = searchWikipedia("me")

// no requests are performed, no work is being done, no URL requests were fired

let cancel = searchForMe
  // sequence generation starts now, URL requests are fired
  .subscribe(onNext: { results in
      print(results)
  })

有很多方法可以创建自定义的 Observable 序列,最简单的方式大概就是使用 create 方法

RxSwift 提供了一个方法来创建一个返回一个元素给订阅者的序列,这个方法就是 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 方法呢?

它只是一个可以让你容易使用 Swift 的闭包实现 sbuscribe 方法的便利方法。就像 subscribe 方法一样,create 方法需要一个参数,observer,然后返回 disposable 对象

序列的这种实现实际上是同步的,它将会生成元素然后在 subscribe 调用返回 disposable 之前终止。因此,返回什么 disposable 并不重要,生成元素的过程不会被打断。

当生成同步序列的时候,通常返回的 disposable 对象是 NopDisposable 的单例。

来看另一个例子,创建 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 ----

Creating an Observable that performs work

尝试一下更有意思的事情,创建一个前面用到过的 interval 操作符

下面的实现等价于 dispatch queue schedulers

func myInterval(_ interval: DispatchTimeInterval) -> Observable<Int> {
    return Observable.create { observer in
        print("Subscribed")
        let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
        timer.schedule(deadline: DispatchTime.now() + interval, repeating: interval)

        let cancel = Disposables.create {
            print("Disposed")
            timer.cancel()
        }

        var next = 0
        timer.setEventHandler {
            if cancel.isDisposed {
                return
            }
            observer.on(.next(next))
            next += 1
        }
        timer.resume()

        return cancel
    }
}
let counter = myInterval(.milliseconds(100))

print("Started ----")

let subscription = counter
    .subscribe(onNext: { n in
        print(n)
    })


Thread.sleep(forTimeInterval: 0.5)

subscription.dispose()

print("Ended ----")

输出结果是

Started ----
Subscribed
0
1
2
3
4
Disposed
Ended ----

如果写成下面这样

let counter = myInterval(.milliseconds(100))

print("Started ----")

let subscription1 = counter
    .subscribe(onNext: { n in
        print("First \(n)")
    })
let subscription2 = counter
    .subscribe(onNext: { n in
        print("Second \(n)")
    })

Thread.sleep(forTimeInterval: 0.5)

subscription1.dispose()

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

每个对 subscription 的订阅者通常生成自己独立的元素序列。操作符默认是无状态的。无状态的操作符远远多余有状态的

Sharing subscription and share operator

如果我们希望多个 observer 通过同一个订阅共享事件(元素)呢?

有两个事情需要定义:

  • 如何去处理在新的订阅者订阅之前已经被传递过的元素(重传最后一个,重传所有,重传最后的 n 个)
  • 如何决定什么时候触发共享的订阅(引用数,手动触发,其它的算法)

通常的选择是 replay(1).refCount() 的组合,也就是 share(replay: 1)

let counter = myInterval(.milliseconds(100))
    .share(replay: 1)

print("Started ----")

let subscription1 = counter
    .subscribe(onNext: { n in
        print("First \(n)")
    })
let subscription2 = counter
    .subscribe(onNext: { n in
        print("Second \(n)")
    })

Thread.sleep(forTimeInterval: 0.5)

subscription1.dispose()

Thread.sleep(forTimeInterval: 0.5)

subscription2.dispose()

print("Ended ----")

This will print

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

现在只有一个 SubscribedDisposed 事件

URL observables 的表现也是等价的

下面是用 Rx 对 HTTP 请求的封装,和 interval 的模式非常相近

extension Reactive where Base: URLSession {
    public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
        return Observable.create { observer in
            let task = self.base.dataTask(with: request) { (data, response, error) in

                guard let response = response, let data = data else {
                    observer.on(.error(error ?? RxCocoaURLError.unknown))
                    return
                }

                guard let httpResponse = response as? HTTPURLResponse else {
                    observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
                    return
                }

                observer.on(.next((httpResponse, data)))
                observer.on(.completed)
            }

            task.resume()

            return Disposables.create {
                task.cancel()
            }
        }
    }
}

Operators

RxSwift 实现了很多操作符

所有操作符的弹珠图可以查看http://reactivex.io/

几乎所有的操作符都在 Playgrounds 中有演示

如果你需要一个操作符但是又不知道怎么找到它,可以查看操作符决策树

Custom operator

有两种方式可以可以创建自定义的操作符

Easy way

内部的代码都是用了高度优化版本的操作符,所以他们不是最好的辅导材料。这也是为什么鼓励使用标准操作符的原因

幸运的有一种简单的方式去创建操作符。创建新的操作符实际上就是创建 observable,并且前面的章节已经描述了怎么做

下面我们来看看如何实现一个未优化过的 map 操作符

extension ObservableType {
    func myMap<R>(transform: @escaping (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(.milliseconds(100))
    .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
...

Life happens

如果在某些情况下用自定义的操作符难以解决问题呢?你可以跳出 Rx 的单一世界,用命令式编程的方式执行任务,然后使用 Subject 再将结果再传递给 Rx

这不是一件可以经常做的事情,可能会导致不良代码,但是还是可以实现的。

  let magicBeings: Observable<MagicBeing> = summonFromMiddleEarth()

  magicBeings
    .subscribe(onNext: { being in     // exit the Rx monad
        self.doSomeStateMagic(being)
    })
    .disposed(by: 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 = BehaviorRelay(value: firstKitten) // again back in Rx monad

  kittens.asObservable()
    .map { kitten in
      return kitten.purr()
    }
    // ....

当你这么做的时候,其他人可能会在其它地方些这样的代码👇

  kittens
    .subscribe(onNext: { kitten in
      // do something with kitten
    })
    .disposed(by: disposeBag)

所以尽量不要这样做

Playgrounds

如果你不确定某些操作符如何工作,playgrounds 几乎包括了所有的操作符,并且准备了小例子来描述他们的表现

使用 playgrounds:

  1. 打开 Rx.xcworkspace
  2. build RxSwift 的 macOS scheme
  3. 打开 Rx.playground

查看 playgrounds 中例子的结果,需要打开 Assistant EditorView > Assistant Editor > Show Assistant Editor

Error handling

有两种错误处理机制

Asynchronous error handling mechanism in observables

错误处理非常直接,如果一个序列因为错误终止,那么所有依赖的序列都会因为错误终止,符合通常的短路逻辑

你可以使用 catch 操作符来恢复 observable 的失败,catch 操作符中还在基本的 error 信息基础上有各种各样的补充信息,可以让你以更细节的方式恢复序列

还可以使用 retry 操作符来重试错误的序列

Debugging Compile Errors

当编写优雅的 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))
            }
      }

如果编译器报告了错误,可以先增加 catch 的返回值声明

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: Error) -> Observable<JSON> in
                return just(JSON(1))
            }
      }

建议首先增加闭包的返回值和参数的声明

通常在你解决了错误之后,你就可以删除添加的声明以保持代码的整洁

Debugging

只使用调试器是有用的,但是通常使用 debug 操作符将会更加的有效率。debug 操作符会打印所有的事件到标准输出并且你可以添加自定义的标签

debug 的表现类似于探针,下面是一个使用例子:

let subscription = myInterval(.milliseconds(100))
    .debug("my probe")
    .map { e in
        return "This is simply \(e)"
    }
    .subscribe(onNext: { n in
        print(n)
    })

Thread.sleepForTimeInterval(0.5)

subscription.dispose()

will print

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

Enabling Debug Mode

为了可以 Debug memory leaks using RxSwift.Resources 或者 Log all HTTP requests automatically,你必须开启 Debug 模式

为了开启 debug 模式,一个 TRACE_RESOURCES 标志必须加到 RxSwift target 的 build settings 中,在 Other Swift Flags 选项下

对于如何在 Cocoapods 和 Carthage 中添加 TRACE_RESOURCES 标志的进一步的讨论和介绍,查看 #378

Debugging memory leaks

在 debug 模式下,Rx 用 Resources.total 追踪所有分配的资源

如果你想添加一些资源泄漏的检测逻辑,最简单的方法就是周期输出 RxSwift.Resources.total

    /* add somewhere in
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil)
    */
    _ = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
        .subscribe(onNext: { _ in
            print("Resource count \(RxSwift.Resources.total)")
        })

测试内存泄漏最有效率的方式是:

  • 打开你的页面并使用
  • 关闭你的页面
  • 观察初始的资源数量
  • 再次打开你的页面并使用
  • 关闭你的页面
  • 观察最终的资源数量

如果开始的资源数量和最终的资源数量不同,那么就有可能出现了内存泄漏

两次打开页面的原因是第一次打开强制加载了 lazy 的资源

KVO

KVO 是一种 Objective-C 的机制,意味着它不是伴随着类型安全来构建的,Rx 也在尝试解决一些这样的问题

Rx 有两种内建的支持 KVO 的方式

// KVO
extension Reactive where Base: NSObject {
    public func observe<E>(type: E.Type, _ keyPath: String, options: KeyValueObservingOptions, retainSelf: Bool = true) -> Observable<E?> {}
}

#if !DISABLE_SWIZZLING
// KVO
extension Reactive where Base: NSObject {
    public func observeWeakly<E>(type: E.Type, _ keyPath: String, options: KeyValueObservingOptions) -> Observable<E?> {}
}
#endif

一个观察 UIView 的 frame 的例子

警告:UIKit 不服从 KVO, 但它可以工作

view
  .rx.observe(CGRect.self, "frame")
  .subscribe(onNext: { frame in
    ...
  })

or

view
  .rx.observeWeakly(CGRect.self, "frame")
  .subscribe(onNext: { frame in
    ...
  })

rx.observe

rx.observe 更高效,因为它这是对 KVO 机制的一个简单封装,但是使用场景也更加局限

  • 可以用在观察 self 开始的或者所有权图(retainSelf = false) 的祖先(持有关系)开始的 path
  • 可以用在观察所有权图(retainSelf = false)的后代(持有关系)开始的 path
  • path 只能包含 strong 修饰的属性,否则可能会有因为在 dealloc 之前没有取消注册 KVO 而导致 crash

E.g.

self.rx.observe(CGRect.self, "view.frame", retainSelf: false)

rx.observeWeakly

rx.observeWeakly 有时相对于 rx.observe 会有一点慢,因为在弱引用的时候需要处理对象的释放

rx.observe 适用的场景 rx.observeWeakly 都可以用,另外还可以用在

  • 因为它不会 retain 观察的对象,它可以用在任何持有关系不明确的对象上
  • 可以用来观察 weak 属性

E.g.

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

Observing structs

KVO 是一个 OC 的机制,所以它严重依赖于 NSValue

RxCocoa 内建了 KVO 观察 CGRect, CGSizeCGPoint 结构体的支持

当观察其它结构体的时候,需要手动从 NSValue 提取出这些结构体

这里是一些例子,描述了如何通过实现 KVORepresentable 协议来对其它结构体提取 KVO 观察机制和 rx.observe* 方法

UI layer tips

当绑定到 UIKit 的 control 上时,你的 Observable 必须要满足 UI 层的一些条件

Threading

Observable 需要在 MainScheduler(UIThread) 发送值,这是 UIKit/Cocoa 的要求

让你的 API 在 MainScheduler 返回结果通常是一个好主意。如果你尝试在后台线程绑定一些东西到 UI 上时,在 Debug 的 build 模式下,RxCocoa 通常会报错一个错误来提示你

你需要添加 observeOn(MainScheduler.instance) 来解决这个问题

URLSession extension 默认不在 MainScheduler 返回结果

Errors

你不能绑定失败到 UIKit control 上,因为那是未定义的表现

如果你不知道 Observable 是否会失败,你可以使用 catchErrorJustReturn(valueThatIsReturnedWhenErrorHappens) 来保证它不会失败,但是当错误发生之后,它的序列仍会完成

如果希望序列继续生产元素,就需要一些版本的 retry操作符

Sharing subscription

你通常希望在 UI 层共享订阅,你不希望不同的 HTTP 请求绑定同样的数据到多个 UI 控件上去

假设你有下面的实现:

let searchResults = searchText
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query in
        API.getSearchResults(query)
            .retry(3)
            .startWith([]) // clears results on new search term
            .catchErrorJustReturn([])
    }
    .share(replay: 1)    // <- notice the `share` operator

通常你想要的是计算之后共享搜索的结果,这就是 share 的意义

UI 层中,在变换链的最后添加 shard 通常是一个好的规则,因为你最想共享计算的结果。你不想在绑定 searchResults 到多个 UI 控件的时候触发不同的 HTTP 连接

也看一下 Driver 单元。它是被设计用来透明地封装这些 share 调用的,确保元素在主线程被观察并且没有错误绑定到 UI 上

Making HTTP requests

制作 HTTP 请求是人们尝试的第一件事

首先你要创建 URLRequest 对象来表示需要被完成的工作,Request 确定请求类型(GET, POST…),是否有请求体,请求参数等

下面是一个简单的 GET 请求的例子

let req = URLRequest(url: URL(string: "http://en.wikipedia.org/w/api.php?action=parse&page=Pizza&format=json"))

如果你想要仅执行了这个请求而不是和其它的 observable 组合,下面的代码就可以实现

let responseJSON = URLSession.shared.rx.json(request: req)

// no requests will be performed up to this point
// `responseJSON` is just a description how to fetch the response


let cancelRequest = responseJSON
    // this will fire the request
    .subscribe(onNext: { json in
        print(json)
    })

Thread.sleep(forTimeInterval: 3.0)

// if you want to cancel request after 3 seconds have passed just call
cancelRequest.dispose()

URLSession extensions 默认不会在 MainScheduler 返回结果

如果你想要更底层的 response,你可以:

URLSession.shared.rx.response(myURLRequest)
    .debug("my request") // this will print out information to console
    .flatMap { (data: NSData, response: URLResponse) -> Observable<String> in
        if let response = response as? HTTPURLResponse {
            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) // if error happened, this will also print out error to console
    }

Logging HTTP traffic

在 debug 模式下,RxCocoa 默认会记录所有的 HTTP 请求到控制台中,如果你想改变这个表现,可以设置 Logging.URLRequests 过滤器

// read your own configuration
public struct Logging {
    public typealias LogURLRequest = (URLRequest) -> Bool

    public static var URLRequests: LogURLRequest =  { _ in
    #if DEBUG
        return true
    #else
        return false
    #endif
    }
}

RxDataSources

是一个实现了 UITableViews 和 UICollectionView 全功能交互数据源的类集合

RxDataSources 打包在这里.

如何使用它们的全功能示范在 RxExample 项目

参考

  1. https://medium.com/gett-engineering/rxswift-share-ing-is-caring-341557714a2d

留下评论