天天看点

RxSwift文档三(基础)

代码下载

开始

该项目试图与ReactiveX.io保持一致。一般的跨平台文档和教程应该对RxSwift也是有效的。

  1. Observables又叫做序列
  2. Disposing
  3. 隐含的Observable保证
  4. 创建第一个Observable
  5. 创建一个Observable执行工作
  6. 共享订阅和share操作符
  7. 操作符
  8. Playgrounds
  9. 自定义操作符
  10. 错误处理
  11. 调试编译错误
  12. 调试
  13. 启用调试模式
  14. 调试内存泄漏
  15. KVO
  16. UI层提示
  17. 发送HTTP请求
  18. RxDataSources

Observables又名序列

基础

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

每个Observable序列只是一个序列。Observable相较Swift的Sequence关键优势在于它还可以异步获取元素。这是RxSwift的核心,这里的文档是阐述这个思想的。

  • Observable(ObservableType)相当于Sequence
  • ObservableType.subscribe方法等同于Sequence.makeIterator方法。
  • Observer(回调)通过ObservableType.subscribe方法以获取序列元素,而不是调用返回的迭代器的next()。

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

人是具有巨大视觉皮层的生物。可以轻松地形象化一个概念时,可以更容易理解它。

可以通过尝试模拟每个Rx操作内的事件状态机到序列上的高级操作来解除许多认知负担。

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

列表和序列是一个重要的概念之一。

这是一系列数字:

--1--2--3--4--5--6--| // 正常结束
           

另一个字符序列

--a--b--a--a--a---d---X // 错误结束
           

一些序列是有限的,而其他序列是无限的,就像一系列按钮点击:

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

这些被称为纹理图。rxmarbles.com。

如果我们将序列语法指定为正则表达式,它将如下所示:

next* (error | completed)?
           

这描述了以下内容:

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

Rx中的序列由推送接口(又称回调)描述。

enum Event<Element>  {
    case next(Element)      // 序列的下一个元素
    case error(Swift.Error) // 序列因错误失败
    case completed          // 序列成功结束
}

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

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

当序列发送completed或error事件时,将释放计算序列元素的所有内部资源。

要立即取消序列元素和免费资源的生成,请在返回的subscription中调用dispose。

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

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

使用dispose bags或takeUntil操作是确保清理资源的有效方法。建议在生产中使用它们,即使序列将在有限时间内终止。

Disposing

可观察的序列可以以另外一种方法终止。当序列完成并且我们想要释放分配用于计算即将到来的元素的所有资源时,可以调用subscription的dispose。

以下是interval操作的示例。

let scheduler = SerialDispatchQueueScheduler(qos: .default)
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler).subscribe { (event) in
    print("\(event)")
}
        
Thread.sleep(forTimeInterval: 2)

subscription.dispose()
           

将打印:

next(0)
next(1)
next(2)
next(3)
next(4)
next(5)
           

请注意,通常不想手动调用dispose; 这只是一个例子。手动调用dispose通常是一种糟糕的代码味道。有更好的方法来清理订阅比如DisposeBag、takeUntil操作,或者一些其他机制。

那么这段代码可以在dispose执行调用后打印出来吗?答案是:这取决于。

  • 如果scheduler是一个串行调度程序(例如MainScheduler)并在同一个串行调度程序上调用dispose,那就不行。
  • 否则是可以的。

只需要并行发生两个进程。

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

问题是“可以在之后打印某些内容?” 这些进程在不同的调度程序上的情况下甚至没有意义。

还有一些例子是可以保证的(在这里解释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被销毁时,它将调用添加的每个disposables的dispose。

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

self.disposeBag = DisposeBag()
           

这将清除旧的引用并导致资源被清理。

如果仍需要明确的手动清理,使用

CompositeDisposable

。它有所需的行为,但一旦调用dispose方法,它将立即处理任何新添加的disposable。

Take until

在dealloc中自动清理订阅的其他方法是使用takeUntil操作。

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

隐含的Observable保证

还有一些额外的保证,所有序列生产者(Observable)必须遵守。

它们生成元素的线程无关紧要,但如果它们生成一个元素并将其发送给观察者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有一个重要的事情要理解。

创建observable时,它不会仅仅因为它已创建而执行任何工作。

确实Observable可以通过多种方式生成元素。其中一些会导致副作用,其中一些会影响现有的运行过程,例如点击鼠标事件等。

然而,如果只是调用返回Observable的方法,则不执行序列生成且没有副作用。Observable只定义如何生成序列以及用于元素生成的参数。subscribe调用方法时开始生成序列。

例如,假设你有一个类似原型的方法:

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

// 不执行任何请求,不执行任何工作,不触发任何URL请求

let cancel = searchForMe
  // 开始执行实际工作
  .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闭包轻松实现subscribe方法。它接受一个参数observer,并返回disposable。

以这种方式实现的序列实际上是同步的。 它将在调用subscribe返回表示订阅的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 ----")

// 首先
stringCounter
    .subscribe(onNext: { n in
        print(n)
    })

print("----")

// 接着
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)")
    })
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 ----
           

订阅后的每个订阅者通常会生成自己独立的元素序列。默认情况下,操作符是无状态的。无状态的操作符远远多于有状态操作符。

共享订阅和share操作符

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

有两件事需要定义。

  • 如何处理新订阅者观察它们之前收到的通过元素(仅replay最新,replay全部,replay最后一个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
First 5
Second 5
Second 6
Second 7
Second 8
Second 9
Disposed
Ended ----
           

请注意现在只有一个Subscribed和Disposed事件。

可观察URL对象的行为是等效的。

这是在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,build RxSwift-macOS scheme,然后在Rx.xcworkspace树视图中打开Playgrounds。

如果需要一个操作符但不知道如何找到它,这里有一个操作符决策树。

自定义操作符

有两种方法可以创建自定义操作符。

简单的方法

所有内部代码都使用高度优化版本的操作符,因此这不是最好的教程材料。这就是为什么鼓励使用标准操作符。

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

让我们看看如何实现未优化的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
...
           

特殊情况

那么如果用自定义运算符解决某些情况太难了呢?可以退出Rx monad,在命令性世界中执行操作,然后使用Subject将结果再次传输到Rx。

这不是经常应该实践的东西,而且代码意味很差,但可以做到。

let magicBeings: Observable<MagicBeing> = summonFromMiddleEarth()

  magicBeings
    .subscribe(onNext: { being in     // 退出 Rx monad
        self.doSomeStateMagic(being)
    })
    .disposed(by: disposeBag)

  //
  //  凌乱的代码
  //
  let kitten = globalParty(   // 凌乱的计算一些东西
    being,
    UIApplication.delegate.dataSomething.attendees
  )
  kittens.on(.next(kitten))   // 把结果发送回Rx
  //
  // 其它凌乱的代码
  //

  let kittens = BehaviorRelay(value: firstKitten) // 重新回到 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,请打开Rx.xcworkspace,build RxSwift-macOS scheme,然后在Rx.xcworkspace树视图中打开Playgrounds。

要在playgrounds中查看示例的结果,请打开Assistant Editor。可以Assistant Editor点击打开View > Assistant Editor > Show Assistant Editor

错误处理

有两种错误机制。

可观察序列中的异步错误处理机制

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

可以使用catch运算符从失败的可观察序列中恢复。有各种重载可以指定详细恢复。

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

Hooks和默认错误处理

RxSwift提供了一个全局Hook,当没有提供自己的

onError

处理序的情况下它提供了默认的错误处理机制。

如果需要,用自己的闭包设置

Hooks.defaultErrorHandler

来决定如何处理系统中未处理的错误。例如,将堆栈跟踪或未跟踪的错误发送到你的分析系统。

默认情况下,

Hooks.defaultErrorHandler

仅在DEBUG模式下打印收到的错误,而在RELEASE模式下不执行任何操作。但是,可以添加其他配置到此行为中。

为了启用详细的调用堆栈日志记录,请将

Hooks.recordCallStackOnError

标志设置为true。

默认情况下,将返回DEBUG模式当前

Thread.callStackSymbols

状态,并在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()
            }
        }
    }
 }
           

启用调试模式

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

为了启用调试模式,必须向RxSwift target build settings中在Other Swift Flags下添加一个TRACE_RESOURCES标志。

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

调试内存泄漏

在调试模式下,Rx在全局变量Resources.total中跟踪所有的已分配资源。

如果想要一些资源泄漏检测逻辑,最简单的方法是定期打印输出RxSwift.Resources.total。

/* 添加在这里
    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的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)
  • 路径必须只包含strong属性,否则有可能在dealloc之前不注销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")
           

观察结构体

KVO是一种Objective-C机制,因此它在很大程度上依赖于NSValue。

RxCocoa已经支持观察CGRect,CGSize和CGPoint结构。

在观察其他一些结构时,必须从NSValue手动提取这些结构。

这里是如何通过实现KVORepresentable协议扩展KVO观察机制和rx.observe*其他结构的方法示例。

UI图层提示

Observable绑定到UIKit控件时,需要在UI层中满足某些要求。

线程

Observable需要在MainScheduler(UIThread)中发送值。这仅仅是UIKit/Cocoa的基本要求。

通常期望API可以在MainScheduler返回结果。如果你试图从后台线程绑定到UI,在Debug时RxCocoa通常会抛出异常来通知你。

要修复此问题,需要添加observeOn(MainScheduler.instance)。

默认情况下,URLSession扩展不会在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([]) // 清除新搜索项的结果
            .catchErrorJustReturn([])
    }
    .share(replay: 1)    // <- 注意share操作符
           

通常想要的是在计算后共享搜索结果。这就是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)

// 到目前为止还没有任何请求被执行
// `responseJSON` 只是一个如何获取响应的描述


let cancelRequest = responseJSON
    // 这将触发请求
    .subscribe(onNext: { json in
        print(json)
    })

Thread.sleep(forTimeInterval: 3.0)

// 如果想在3秒后取消请求,只需要调用
cancelRequest.dispose()
           

默认情况下,URLSession扩展不会在MainScheduler中返回结果。

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

URLSession.shared.rx.response(myURLRequest)
    .debug("my request") // 这将打印出信息到控制台
    .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) // 如果发生错误,这也会向控制台输出错误
    }
           

记录HTTP流量

在调试模式下,RxCocoa默认将所有HTTP请求记录到控制台。如果想要更改该行为,设置

Logging.URLRequests

filter。

// 阅读自己的配置
public struct Logging {
    public typealias LogURLRequest = (URLRequest) -> Bool

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

RxDataSources

…是一组为UITableViews和UICollectionViews 实现响应数据源的全部功能的类。

RxDataSources都在这里。

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