RxSwift Getting Started 官方文档中文版

870 阅读10分钟

入门

这个项目试图与ReactiveX.io保持一致。一般的跨平台文档和教程在RxSwift.

Observables 又名序列

基本

观察者模式 ( sequence) 和正常序列的等价性是理解 Rx 最重要的事情。

每个Observable序列只是一个序列。Observablevs Swift 的Sequence关键优势在于它还可以异步接收元素。这是 RxSwift 的内核,这里的文档是关于我们扩展这个想法的方法。

  • ObservableObservableType) 等价于Sequence
  • ObservableType.subscribe方法等价于Sequence.makeIterator方法。
  • 观察者(回调)需要传递给ObservableType.subscribe方法来接收序列元素,而不是调用next()返回的迭代器。

序列是一个简单、熟悉且易于可视化的概念。

人是具有巨大视觉皮层的生物。当我们可以轻松地可视化一个概念时,推理它就会容易得多。

我们可以将大量的认知负荷从尝试模拟每个 Rx 运算符内部的事件状态机转移到对序列的高级操作上。

如果我们不使用 Rx 而是为异步系统建模,这可能意味着我们的代码充满了状态机和我们需要模拟而不是抽象的暂态。

列表和序列可能是数学家和程序员最先学习的概念之一。

这是一个数字序列:

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

另一个序列,带有字符:

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

一些序列是有限的,而另一些是无限的,比如一系列的按钮点击:

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

这些被称为弹珠图。rxmarbles.com上有更多弹珠图。

如果我们将序列语法指定为正则表达式,它看起来像:

next* (error | completed)?

这描述了以下内容:

  • 序列可以有 0 个或多个元素。
  • 一旦接收到errororcompleted事件,序列就不能产生任何其他元素。

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

当一个序列发送completedorerror事件时,所有计算序列元素的内部资源都将被释放。

要立即取消生产序列元素和释放资源,请调用dispose返回的订阅。

如果一个序列在有限时间内终止,不调用dispose或不使用disposed(by: disposeBag)不会导致任何永久性资源泄漏。但是,这些资源将一直使用到序列完成,或者通过完成元素的生成或返回错误。

如果序列没有自行终止,例如通过一系列按钮点击,则资源将被永久分配,除非dispose手动调用、自动在disposeBag内调用、使用takeUntil操作符或以其他方式调用。

使用清除袋或takeUntil操作符是确保资源清理的一种可靠方式。我们建议在生产中使用它们,即使序列将在有限时间内终止。

如果你好奇为什么Swift.Error不是通用的,你可以在这里找到解释。

清除

一个被观察了的序列有一个额外的方法被终止。当我们完成一个序列然后想去释放所有已分配的资源,我们可以在一个订阅上调用 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()

这将打印:

0
1
2
3
4
5

请注意,您通常不想手动调用dispose; 这只是一个示例。手动调用 dispose 通常是一种不好的代码气味。有更好的方法来处理订阅,例如DisposeBagtakeUntil操作符或其他一些机制。

那么这段代码可以在dispose调用执行后打印一些东西吗?答案是:视情况而定。

  • 如果scheduler串行调度(例如MainScheduler)并且在同一个串行调度上dispose被调用,则答案是否定的。
  • 否则是可以的。

您可以在此处找到有关调度程序的更多信息。

你只是有两个并行发生的进程。

  • 一是生产元素
  • 另一个正在处理订阅

“能在之后打印东西?”这个问题根本没有意义如果进程在两个不同的进程上。

再举几个例子(这里解释了observeOn)。

如果我们有类似的东西:

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

// ....

subscription.dispose() // called from main thread

调用返回后dispose,不会打印任何内容。这是肯定的。

此外,在这种情况下:

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

// ...

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

调用返回后dispose,不会打印任何内容。这是肯定的。

DisposeBag

Dispose 包用于将类似 ARC 的行为返回给 RX。

DisposeBag被释放时,它将调用dispose添加的每个可处置对象。

它没有dispose方法,因此不允许故意显式调用 dispose。如果需要立即清理,我们可以创建一个新包。

  self.disposeBag = DisposeBag()

这将清除旧引用并导致资源处置。

如果仍然需要显式手动处理,请使用CompositeDisposable它有想要的行为,但一旦dispose方法被调用,它将立即处理任何新添加的disposable方法。

Take until

还有一个销毁时自动处理订阅的方法,那就是使用 takeUntil 操作符。

sequence
    .take(until: self.rx.deallocated)
    .subscribe {
        print($0)
    }

隐式Observable保证

还有一些额外的保证是所有序列生成器(Observables)必须遵守的。

它们在哪个线程上生成元素并不重要,但如果它们生成一个元素并将其发送给观察者,则在方法完成执行observer.on(.next(nextElement))之前它们不能发送下一个元素。 生产者也不能发送终止的.completed.error,以防.next事件尚未完成

简而言之,考虑这个例子:

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,有一件至关重要的事情需要理解。

当一个 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闭包实现订阅方法。与订阅方法一样,它也接受一个参数observer,并返回disposable。

以这种方式实现的序列实际上是同步的。它将生成表示订阅的元素并在subscribe调用返回disposable之前终止。正因为如此,它返回什么disposable并不重要,生成元素的过程不能被中断。

生成同步序列时,通常返回的一次性对象是NopDisposable的单例对象.

现在让我们创建一个从数组返回元素的obserbable。

这是实际的实现

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运算符。

这相当于实际的派遣队列调度实现

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)")
    })
    
print("Subscribed")

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

Thread.sleep(forTimeInterval: 0.5)

subscription1.dispose()

print("Disposed")

Thread.sleep(forTimeInterval: 0.5)

subscription2.dispose()

print("Disposed")

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

订阅后的每个订阅者通常都会生成自己独立的元素序列。运算符默认是无状态的。无状态操作符比有状态操作符多得多。

分享订阅和share操作符

但是,如果您希望多个观察者共享来自一个订阅的事件(元素),该怎么办呢?

有两件事需要定义。

  • 如何处理在新订阅者有兴趣观察它们之前收到的过去元素(仅重播最近的,重播所有,重播最后n个)
  • 如何决定何时触发该共享订阅(refCount、手动或其他算法)

通常的选择是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 ----")

这将打印

Started ----
Subscribed
First 0
Second 0
First 1
Second 1
First 2
Second 2
First 3
Second 3
First 4
Second 4
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()
            }
        }
    }
}

操作符

RxSwift 中实现了许多运算符。

所有操作的弹珠图都可以在ReactiveX.io上找到

几乎所有运算符都在Playgrounds中进行了演示。

要使用Playgrounds,请打开Rx.xcworkspace、构建RxSwift-macOS方案,然后在Rx.xcworkspace树视图中打开Playgrounds。

如果您需要操作符,但不知道如何找到它,则可以使用操作符的决策树

自定义运算符

有两种方法可以创建自定义运算符。

简单的方法

所有内部代码都使用高度优化的运算符版本,因此它们不是最好的自学材料。这就是为什么强烈鼓励使用标准运算符的原因。

幸运的是,有一种更简单的方法来创建运算符。创建新的操作符实际上就是创建 observables,前一章已经描述了如何做到这一点。

让我们看看如何实现未优化的map运算符。

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

Infallible

Infallible是另一种与Observable它相同的风格,但保证永远不会失败,因此不会发出错误。这意味着在创建您自己的Infallible(使用Infallible.create创建您的第一个Observable中提到的方法之一)时,您将不会被允许发出错误。

当您想要静态建模并保证一个已知永远不会失败的值流,但不想使用MainScheduler,也不想隐式地使用share()来共享资源和副作用时,如Driver和Signal中的情况,Infallible非常有用。

权宜之计

那么,如果使用自定义运算符解决某些情况太难了怎么办?您可以退出 Rx monad,在命令式世界中执行操作,然后使用Subjects 再次将结果隧道传输到 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

如果您不确定某些运算符的工作原理,Playground包含几乎所有已准备好的运算符,其中包含说明其行为的小示例。

要使用 Playground,请打开 Rx.xcworkspace,构建 RxSwift-macOS 方案,然后在 Rx.xcworkspace 树视图中打开 Playground。

要查看 Playground 中示例的结果,请打开Assistant Editor. 你可以Assistant Editor点击打开View > Assistant Editor > Show Assistant Editor

错误处理

有两种错误机制。

0bservable中的异步错误处理机制

错误处理非常简单。如果一个序列因错误而终止,则所有相关序列都将因错误而终止。这是通常的短路逻辑。

你可以通过使用操作符从 observable 的失败中恢复catch。有各种重载使您能够非常详细地指定恢复。

还有一个retry运算符可以在序列错误的情况下重试。

调试编译错误

RxSwift 提供了一个全局 Hook,它为您不提供自己的onError处理程序的情况提供默认的错误处理机制。

如果您需要该选项,请Hooks.defaultErrorHandler使用您自己的闭包来决定如何处理系统中未处理的错误。例如,将堆栈跟踪或未跟踪错误发送到您的分析系统。

默认情况下,仅在DEBUGmode 中Hooks.defaultErrorHandler打印接收到的错误,而在RELEASE中什么也不做. 但是,您可以为此行为添加其他配置。

为了启用详细的调用堆栈日志记录,请将Hooks.recordCallStackOnErrorflag 设置为true.

默认情况下,这将返回当前Thread.callStackSymbolsDEBUG模式,并在RELEASE跟踪一个空的堆栈. 您可以通过覆盖Hooks.customCaptureSubscriptionCallstack您自己的实现来自定义此行为。

调试编译错误

在编写优雅的 RxSwift/RxCocoa 代码时,您可能严重依赖编译器来推断Observables 的类型。这是 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: Error) -> Observable<JSON> in
                return just(JSON(1))
            }
      }

我建议首先注释闭包的返回类型和参数。

通常在您修复错误后,您可以删除类型注释以再次清理您的代码。

调试

单独使用调试器很有用,但通常使用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()

将打印

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

启用调试模式

为了自动记录所有 HTTP 请求或者使用RxSwift.Resources调试内存泄漏,您必须启用调试模式。

为了启用调试模式,必须在RxSwift目标构建设置的Other Swift Flags下添加TRACE_RESOURCES标志。 

关于如何为Cocoapods和Carthage设置TRACE_RESOURCES标志的进一步讨论和说明,请参见 #378

调试内存泄漏

在调试模式下,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)")
        })

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

  • 导航到您的屏幕并使用它
  • 返回
  • 观察初始资源计数
  • 再次导航到你的屏幕并使用它
  • 返回
  • 观察最终资源计数

如果初始资源计数和最终资源计数之间存在差异,那么可能在某个地方存在内存泄漏。

之所以建议使用2个导航,是因为第一个导航会导致惰性资源的加载。

KVO

KVO 是一种 Objective-C 机制。这意味着它在构建时没有考虑到类型安全。这个项目试图解决一些问题。

这个库有两种内置方式支持 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.

警告: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 机制的简单包装,但它的使用场景更有限

  • 他可以被用来观察所有权图(retainSelf = false)中从 self 或者其祖先开始的路径
  • 他可以被用来观察所有权图(retainSelf = true)中从其子类开始的路径
  • 路径中不得不只能包含 strong 属性,否则你的系统就有崩溃的风险,因为没有在释放前注册KVO观察者。

例如

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

rx.observeWeakly

rx.observeWeakly比它要慢一些,rx.observe因为它必须在弱引用的情况下处理对象释放。

它可以被用在所有 rx_observe 可以用的地方,另外

  • 因为它不会保留被观察的目标,所以它能被用来观察任意所有权关系不明的对象图
  • 它可以用来观察weak属性

例如

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

观察structs

KVO 是一种 Objective-C 机制,因此它严重依赖NSValue.

RxCocoa 内置了对 KVO 观察CGRect,CGSizeCGPointstructs 的支持。

当观察其他结构时,需要手动从NSValue中提取这些结构。

这里是如何通过实现 KVORepresentable 协议,为其他结构扩展KVO观察机制和 rx_observe* 方法的例子

UI层提示

Observable在绑定到 UIKit 控件时,您的用户需要在 UI 层中满足某些事情。

线程

Observables 需要在MainScheduler(UIThread) 上发送值。这只是一个正常的 UIKit/Cocoa 要求。

您的 API 在MainScheduler. 如果您尝试从后台线程将某些内容绑定到 UI,在Debug构建中,RxCocoa 通常会抛出异常来通知您。

要解决此问题,您需要添加observeOn(MainScheduler.instance).

NSURLSession扩展默认不在 MainScheduler 上返回结果

错误

您不能将失败绑定到 UIKit 控件,因为这是未定义的行为。

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

如果想要之后的序列继续产生元素,那就需要 retry 操作符。

分享订阅

您通常希望在 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 层中share添加到转换链的末尾通常是一个很好的经验,因为您确实希望共享计算结果。您不想在绑定searchResults到多个 UI 元素时触发单独的 HTTP 连接。

另外看一下Driver单位。它被设计成透明地包装这些share调用,确保在主 UI 线程上观察到元素,并且不会将错误绑定到 UI。

发出 HTTP 请求

发出http请求是人们首先尝试的事情之一。

您首先需要构建URLRequest代表需要完成的工作的对象。

请求确定是 GET 请求,还是 POST 请求,请求正文是什么,查询参数...

这是创建简单 GET 请求的方法

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

如果您只想在与其他可观察对象组合之外执行该请求,则需要执行此操作。

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

NSURLSession 扩展默认不在 MainScheduler 上返回结果。

如果您想要更低级别的响应访问,您可以使用:

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
    }

HTTP 通信日志

在调试模式下运行时,RxCocoa 默认会将所有 HTTP 请求信息记录到控制台。您可以覆盖URLSession.rx.shouldLogRequest闭包以定义应该和不应该记录哪些请求。

URLSession.rx.shouldLogRequest = { request in
    // Only log requests to reactivex.org     
    return request.url?.host == "reactivex.org" || request.url?.host == "www.reactivex.org"
}

RxDataSources

... 是一组为UITableViews 和UICollectionViews 实现功能齐全的反应式数据源的类。

RxDataSources 捆绑在这里

RxExample项目中包含如何使用它们的完整功能演示。