我们知道Swift中有很多高阶函数,非常好用,而且效率都很高,如我们经常使用的map,fliter,flatmap等等。详情可以参考我之前的一篇文章:Swift的高阶函数
Swift书籍资料下载:下载地址
1. 高阶函数
1.1 算数、以及聚合操作(Mathematical and Aggregate Operators)
1.1.1 toArray
- 该操作符先把一个序列转成一个数组,并作为一个单一的事件发送,然后结束。
- 实例1.1.1
let disposeBag = DisposeBag()
Observable.of(1, 2, 3)
.toArray()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
运行结果:
1.1.2 reduce
- 基本介绍
- reduce 接受一个初始值,和一个操作符号。
- reduce 将给定的初始值,与序列里的每个值进行累计运算。得到一个最终结果,并将其作为单个值发送出去。
- 实例1.1.2
let disposeBag = DisposeBag()
Observable.of(1, 2, 3, 4, 5)
.reduce(0, accumulator: +)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
运行结果:
15
1.1.3 concat
- 基本介绍
- concat 会把多个 Observable 序列合并(串联)为一个 Observable 序列。
- 并且只有当前面一个 Observable 序列发出了 completed 事件,才会开始发送下一个 Observable 序列事件。
- 实例1.1.3
let disposeBag = DisposeBag()
let subject1 = BehaviorSubject(value: 1)
let subject2 = BehaviorSubject(value: 2)
let variable = Variable(subject1)
variable.asObservable()
.concat()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
subject2.onNext(2)
subject1.onNext(1)
subject1.onNext(1)
subject1.onCompleted()
variable.value = subject2
subject2.onNext(2)
运行结果:

1.2 连接操作符:connect、publish、replay、multicast
1.2.1 可连接的序列
- 什么是可连接的序列:
可连接的序列(Connectable Observable):
(1)可连接的序列和一般序列不同在于:有订阅时不会立刻开始发送事件消息,只有当调用 connect() 之后才会开始发送值。
(2)可连接的序列可以让所有的订阅者订阅后,才开始发出事件消息,从而保证我们想要的所有订阅者都能接收到事件消息。
- 在演示可连接序列之前,先看一个普通序列的样例:
- 实例1.2.1
//每隔1秒钟发送1个事件
let interval = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//第一个订阅者(立刻开始订阅)
_ = interval
.subscribe(onNext: { print("订阅1: \($0)") })
//第二个订阅者(延迟5秒开始订阅)
delay(5) {
_ = interval
.subscribe(onNext: { print("订阅2: \($0)") })
}
- 为方便使用,这里我们定义了一个延迟执行方法:
///延迟执行
/// - Parameters:
/// - delay: 延迟时间(秒)
/// - closure: 延迟执行的闭包
public func delay(_ delay: Double, closure: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
closure()
}
}
- 运行结果如下。可以看到第一个订阅者订阅后,每隔 1 秒会收到一个值。而第二个订阅者 5 秒后才收到第一个值 0,所以两个订阅者接收到的值是不同步的。
1.2.2 publish
publish
方法会将一个正常的序列转换成一个可连接的序列。同时该序列不会立刻发送事件,只有在调用connect
之后才会开始。
- 实例1.2.2 :
我们对上面的实例1.2.1进行改造,代码如下:
//每隔1秒钟发送1个事件
let interval = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.publish()
//第一个订阅者(立刻开始订阅)
_ = interval
.subscribe(onNext: { print("订阅1: \($0)") })
//相当于把事件消息推迟了两秒
delay(2) {
_ = interval.connect()
}
//第二个订阅者(延迟5秒开始订阅)
delay(5) {
_ = interval
.subscribe(onNext: { print("订阅2: \($0)") })
}
运行结果:
1.2.3 replay
- 基本介绍
replay
同上面的publish
方法相同之处在于:会将将一个正常的序列转换成一个可连接的序列。同时该序列不会立刻发送事件,只有在调用connect
之后才会开始。replay
与publish
不同在于:新的订阅者还能接收到订阅之前的事件消息(数量由设置的 bufferSize 决定)。
- 实例1.2.3
//每隔1秒钟发送1个事件
let interval = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.replay(5)
//第一个订阅者(立刻开始订阅)
_ = interval
.subscribe(onNext: { print("订阅1: \($0)") })
//相当于把事件消息推迟了两秒
delay(2) {
_ = interval.connect()
}
//第二个订阅者(延迟5秒开始订阅)
delay(5) {
_ = interval
.subscribe(onNext: { print("订阅2: \($0)") })
}
运行结果:
1.2.4 multicast
- 基本介绍
multicast
方法同样是将一个正常的序列转换成一个可连接的序列。- 同时
multicast
方法还可以传入一个Subject
,每当序列发送事件时都会触发这个Subject
的发送。
- 实例 1.2.4 :
//创建一个Subject(后面的multicast()方法中传入)
let subject = PublishSubject<Int>()
//这个Subject的订阅
_ = subject
.subscribe(onNext: { print("Subject: \($0)") })
//每隔1秒钟发送1个事件
let interval = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.multicast(subject)
//第一个订阅者(立刻开始订阅)
_ = interval
.subscribe(onNext: { print("订阅1: \($0)") })
//相当于把事件消息推迟了两秒
delay(2) {
_ = interval.connect()
}
//第二个订阅者(延迟5秒开始订阅)
delay(5) {
_ = interval
.subscribe(onNext: { print("订阅2: \($0)") })
}
运行结果:
1.2.5 refCount
- 基本介绍
refCount
操作符可以将可被连接的Observable
转换为普通Observable
- 即该操作符可以自动连接和断开可连接的
Observable
。当第一个观察者对可连接的Observable
订阅时,那么底层的Observable
将被自动连接。当最后一个观察者离开时,那么底层的Observable
将被自动断开连接。
- 实例 1.2.5
//每隔1秒钟发送1个事件
let interval = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.publish()
.refCount()
//第一个订阅者(立刻开始订阅)
_ = interval
.subscribe(onNext: { print("订阅1: \($0)") })
//第二个订阅者(延迟5秒开始订阅)
delay(5) {
_ = interval
.subscribe(onNext: { print("订阅2: \($0)") })
}
运行结果:
1.2.6 share(replay:)
- 基本介绍
- 该操作符将使得观察者共享源
Observable
,并且缓存最新的n
个元素,将这些元素直接发送给新的观察者。- 简单来说
shareReplay
就是replay
和refCount
的组合。
- 实例1.2.6
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
override func viewDidLoad() {
//每隔1秒钟发送1个事件
let interval = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.share(replay: 5)
//第一个订阅者(立刻开始订阅)
_ = interval
.subscribe(onNext: { print("订阅1: \($0)") })
//第二个订阅者(延迟5秒开始订阅)
delay(5) {
_ = interval
.subscribe(onNext: { print("订阅2: \($0)") })
}
}
}
///延迟执行
/// - Parameters:
/// - delay: 延迟时间(秒)
/// - closure: 延迟执行的闭包
public func delay(_ delay: Double, closure: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
closure()
}
}
运行结果:
1.3 其他操作符:delay、materialize、timeout等
1.3.1 delay
- 该操作符会将
Observable
的所有元素都先拖延一段设定好的时间,然后才将它们发送出来。
- 实例 1.3.1
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
Observable.of(1, 2, 1)
.delay(3, scheduler: MainScheduler.instance) //元素延迟3秒才发出
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
}
}
运行结果:
1
2
1
1.3.2 delaySubscription
- 使用该操作符可以进行延时订阅。即经过所设定的时间后,才对
Observable
进行订阅操作。
- 实例1.3.2
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
Observable.of(1, 2, 1)
.delaySubscription(3, scheduler: MainScheduler.instance) //延迟3秒才开始订阅
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
}
}
运行结果:
1
2
1
1.3.3 materialize
- 基本介绍
- 该操作符可以将序列产生的事件,转换成元素。
通常一个有限的Observable
将产生零个或者多个onNext
事件,- 最后产生一个
onCompleted
或者onError
事件。而materialize
操作符会将Observable
产生的这些事件全部转换成元素,然后发送出来。
- 实例1.3.3
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
Observable.of(1, 2, 1)
.materialize()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
}
}
运行结果:
1.3.4 dematerialize
- 该操作符的作用和
materialize
正好相反,它可以将materialize
转换后的元素还原。
- 实例1.3.4
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
Observable.of(1, 2, 1)
.materialize()
.dematerialize()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
}
}
运行结果:
1
2
1
1.3.5 timeout
- 使用该操作符可以设置一个超时时间。如果源
Observable
在规定时间内没有发任何出元素,就产生一个超时的error
事件。
- 实例1.3.5
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//定义好每个事件里的值以及发送的时间
let times = [
[ "value": 1, "time": 0 ],
[ "value": 2, "time": 0.5 ],
[ "value": 3, "time": 1.5 ],
[ "value": 4, "time": 4 ],
[ "value": 5, "time": 5 ]
]
//生成对应的 Observable 序列并订阅
Observable.from(times)
.flatMap { item in
return Observable.of(Int(item["value"]!))
.delaySubscription(Double(item["time"]!),
scheduler: MainScheduler.instance)
}
.timeout(2, scheduler: MainScheduler.instance) //超过两秒没发出元素,则产生error事件
.subscribe(onNext: { element in
print(element)
}, onError: { error in
print(error)
})
.disposed(by: disposeBag)
}
}
运行j结果:
1.3.6 using
- 使用
using
操作符创建Observable
时,同时会创建一个可被清除的资源,一旦Observable
终止了,那么这个资源就会被清除掉了。
- 实例1.3.6
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
override func viewDidLoad() {
//一个无限序列(每隔0.1秒创建一个序列数 )
let infiniteInterval$ = Observable<Int>
.interval(0.1, scheduler: MainScheduler.instance)
.do(
onNext: { print("infinite$: \($0)") },
onSubscribe: { print("开始订阅 infinite$")},
onDispose: { print("销毁 infinite$")}
)
//一个有限序列(每隔0.5秒创建一个序列数,共创建三个 )
let limited$ = Observable<Int>
.interval(0.5, scheduler: MainScheduler.instance)
.take(2)
.do(
onNext: { print("limited$: \($0)") },
onSubscribe: { print("开始订阅 limited$")},
onDispose: { print("销毁 limited$")}
)
//使用using操作符创建序列
let o: Observable<Int> = Observable.using({ () -> AnyDisposable in
return AnyDisposable(infiniteInterval$.subscribe())
}, observableFactory: { _ in return limited$ }
)
o.subscribe()
}
}
class AnyDisposable: Disposable {
let _dispose: () -> Void
init(_ disposable: Disposable) {
_dispose = disposable.dispose
}
func dispose() {
_dispose()
}
}
运行结果:
1.4 错误处理
1.4.1 catchErrorJustReturn
- 当遇到
error
事件的时候,就返回指定的值,然后结束。 - 实例1.4.1:
let disposeBag = DisposeBag()
let sequenceThatFails = PublishSubject<String>()
sequenceThatFails
.catchErrorJustReturn("错误")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
sequenceThatFails.onNext("a")
sequenceThatFails.onNext("b")
sequenceThatFails.onNext("c")
sequenceThatFails.onError(MyError.A)
sequenceThatFails.onNext("d")
运行结果:
1.4.2 catchError
- 基本介绍
- 该方法可以捕获 error,并对其进行处理。
- 同时还能返回另一个 Observable 序列进行订阅(切换到新的序列)。
- 实例1.4.2:
let disposeBag = DisposeBag()
let sequenceThatFails = PublishSubject<String>()
let recoverySequence = Observable.of("1", "2", "3")
sequenceThatFails
.catchError {
print("Error:", $0)
return recoverySequence
}
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
sequenceThatFails.onNext("a")
sequenceThatFails.onNext("b")
sequenceThatFails.onNext("c")
sequenceThatFails.onError(MyError.A)
sequenceThatFails.onNext("d")
运行结果:
1.4.3 retry
- 基本介绍
- 使用该方法当遇到错误的时候,会重新订阅该序列。比如遇到网络请求失败时,可以进行重新连接。
retry()
方法可以传入数字表示重试次数。不传的话只会重试一次。
- 实例1.4.3
let disposeBag = DisposeBag()
var count = 1
let sequenceThatErrors = Observable<String>.create { observer in
observer.onNext("a")
observer.onNext("b")
//让第一个订阅时发生错误
if count == 1 {
observer.onError(MyError.A)
print("Error encountered")
count += 1
}
observer.onNext("c")
observer.onNext("d")
observer.onCompleted()
return Disposables.create()
}
sequenceThatErrors
.retry(2) //重试2次(参数为空则只重试一次)
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)运行结果如下:
运行结果:
1.5 调试操作
1.5.1 debug
- 我们可以将
debug
调试操作符添加到一个链式步骤当中,这样系统就能将所有的订阅者、事件、和处理等详细信息打印出来,方便我们开发调试。 - 实例1.5.1
let disposeBag = DisposeBag()
Observable.of("2", "3")
.startWith("1")
.debug()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
运行结果:
- debug() 方法还可以传入标记参数,这样当项目中存在多个 debug 时可以很方便地区分出来。
- 实例1.5.1.1
let disposeBag = DisposeBag()
Observable.of("2", "3")
.startWith("1")
.debug("调试1")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
运行结果:
1.5.2 RxSwift.Resources.total
- 通过将
RxSwift.Resources.total
打印出来,我们可以查看当前RxSwift
申请的所有资源数量。这个在检查内存泄露的时候非常有用。 - 实例1.5.2
print(RxSwift.Resources.total)
let disposeBag = DisposeBag()
print(RxSwift.Resources.total)
Observable.of("BBB", "CCC")
.startWith("AAA")
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
print(RxSwift.Resources.total)
运行结果
1.6 特征序列1:Single、Completable、Maybe
- 除了
Observable
,RxSwift 还为我们提供了一些特征序列(Traits
):Single
、Completable
、Maybe
、Driver
、ControlEvent
。
1.6.1 Single
- 基本介绍
- Single 是 Observable 的另外一个版本。但它不像 Observable 可以发出多个元素,它要么只能发出一个元素,要么产生一个 error 事件。
- 发出一个元素,或一个 error 事件
- 不会共享状态变化
- 应用场景:Single 比较常见的例子就是执行 HTTP 请求,然后返回一个应答或错误。不过我们也可以用 Single 来描述任何只有一个元素的序列。
- SingleEvent:是个枚举
success:里面包含该 Single 的一个元素值
.error:用于包含错误
public enum SingleEvent<Element> {
case success(Element)
case error(Swift.Error)
}
- 实例1.6.1 :
(1)创建 Single 和创建 Observable 非常相似。下面代码我们定义一个用于生成网络请求 Single 的函数:
//获取豆瓣某频道下的歌曲信息
func getPlaylist(_ channel: String) -> Single<[String: Any]> {
return Single<[String: Any]>.create { single in
let url = "https://douban.fm/j/mine/playlist?"
+ "type=n&channel=\(channel)&from=mainsite"
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _, error in
if let error = error {
single(.error(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data,
options: .mutableLeaves),
let result = json as? [String: Any] else {
single(.error(DataError.cantParseJSON))
return
}
single(.success(result))
}
task.resume()
return Disposables.create { task.cancel() }
}
}
//与数据相关的错误类型
enum DataError: Error {
case cantParseJSON
}
(2)接着我们可以使用如下方式使用这个 Single:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//获取第0个频道的歌曲信息
getPlaylist("0")
.subscribe { event in
switch event {
case .success(let json):
print("JSON结果: ", json)
case .error(let error):
print("发生错误: ", error)
}
}
.disposed(by: disposeBag)
}
}
(3)也可以使用 subscribe(onSuccess:onError:) 这种方式:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//获取第0个频道的歌曲信息
getPlaylist("0")
.subscribe(onSuccess: { json in
print("JSON结果: ", json)
}, onError: { error in
print("发生错误: ", error)
})
.disposed(by: disposeBag)
}
}
(4)运行结果如下:
1.6.2 asSingle()
- 我们可以通过调用
Observable
序列的.asSingle()
方法,将它转换为Single
。
let disposeBag = DisposeBag()
Observable.of("1")
.asSingle()
.subscribe({ print($0) })
.disposed(by: disposeBag)
运行结果:
1.6.3 Completable
- 基本介绍:
Completable
是Observable
的另外一个版本。不像Observable
可以发出多个元素,它要么只能产生一个completed
事件,要么产生一个error
事件。- 不会发出任何元素
- 只会发出一个 completed 事件或者一个 error 事件
- 不会共享状态变化
- 应用场景:
Completable
和Observable<Void>
有点类似。适用于那些只关心任务是否完成,而不需要在意任务返回值的情况。比如:在程序退出时将一些数据缓存到本地文件,供下次启动时加载。像这种情况我们只关心缓存是否成功。
- CompletableEvent:
为方便使用,RxSwift 为 Completable 订阅提供了一个枚举(CompletableEvent):
- .completed:用于产生完成事件
- .error:用于产生一个错误
public enum CompletableEvent { case error(Swift.Error) case completed }
- 实例1.6.3 :
(1)创建 Completable
和创建 Observable
非常相似。下面代码我们使用 Completable 来模拟一个数据缓存本地的操作:
//将数据缓存到本地
func cacheLocally() -> Completable {
return Completable.create { completable in
//将数据缓存到本地(这里掠过具体的业务代码,随机成功或失败)
let success = (arc4random() % 2 == 0)
guard success else {
completable(.error(CacheError.failedCaching))
return Disposables.create {}
}
completable(.completed)
return Disposables.create {}
}
}
//与缓存相关的错误类型
enum CacheError: Error {
case failedCaching
}
(2)接着我们可以使用如下方式使用这个 Completable
:
cacheLocally()
.subscribe { completable in
switch completable {
case .completed:
print("保存成功!")
case .error(let error):
print("保存失败: \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)
(3)也可以使用 subscribe(onCompleted:onError:) 这种方式:
cacheLocally()
.subscribe(onCompleted: {
print("保存成功!")
}, onError: { error in
print("保存失败: \(error.localizedDescription)")
})
.disposed(by: disposeBag)
(4)运行结果如下(失败的情况):
1.6.4 Maybe
- 基本介绍
Maybe
同样是Observable
的另外一个版本。它介于Single
和Completable
之间,它要么只能发出一个元素,要么产生一个completed
事件,要么产生一个error
事件。
- 应用场景
Maybe
适合那种可能需要发出一个元素,又可能不需要发出的情况。
- MaybeEvent
为方便使用,RxSwift 为 Maybe 订阅提供了一个枚举(MaybeEvent):
.success:里包含该 Maybe 的一个元素值
.completed:用于产生完成事件
.error:用于产生一个错误
public enum MaybeEvent<Element> { case success(Element) case error(Swift.Error) case completed }
实例1.6.4 (1)创建 Maybe 和创建 Observable 同样非常相似:
func generateString() -> Maybe<String> {
return Maybe<String>.create { maybe in
//成功并发出一个元素
maybe(.success("hangge.com"))
//成功但不发出任何元素
maybe(.completed)
//失败
//maybe(.error(StringError.failedGenerate))
return Disposables.create {}
}
}
//与缓存相关的错误类型
enum StringError: Error {
case failedGenerate
}
(2)接着我们可以使用如下方式使用这个 Maybe:
generateString()
.subscribe { maybe in
switch maybe {
case .success(let element):
print("执行完毕,并获得元素:\(element)")
case .completed:
print("执行完毕,且没有任何元素。")
case .error(let error):
print("执行失败: \(error.localizedDescription)")
}
}
.disposed(by: disposeBag)
(3)也可以使用 subscribe(onSuccess:onCompleted:onError:) 这种方式:
generateString()
.subscribe(onSuccess: { element in
print("执行完毕,并获得元素:\(element)")
},
onError: { error in
print("执行失败: \(error.localizedDescription)")
},
onCompleted: {
print("执行完毕,且没有任何元素。")
})
.disposed(by: disposeBag)
运行结果:
1.6.5 asMaybe()
-(1)我们可以通过调用 Observable 序列的 .asMaybe() 方法,将它转换为 Maybe。
let disposeBag = DisposeBag()
Observable.of("1")
.asMaybe()
.subscribe({ print($0) })
.disposed(by: disposeBag)
运行结果如下:
1.7 特征序列2:Driver
1.7.1 基本介绍
(1)Driver 可以说是最复杂的 trait,它的目标是提供一种简便的方式在 UI 层编写响应式代码。
(2)如果我们的序列满足如下特征,就可以使用它:
A. 不会产生 error 事件
B. 一定在主线程监听(MainScheduler)
C. 共享状态变化(shareReplayLatestWhileConnected)
1.7.2 为什么要使用 Driver?
(1)Driver 最常使用的场景应该就是需要用序列来驱动应用程序的情况了,比如:
- 通过 CoreData 模型驱动 UI
- 使用一个 UI 元素值(绑定)来驱动另一个 UI 元素值
(2)与普通的操作系统驱动程序一样,如果出现序列错误,应用程序将停止响应用户输入。
(3)在主线程上观察到这些元素也是极其重要的,因为 UI 元素和应用程序逻辑通常不是线程安全的。
(4)此外,使用构建 Driver 的可观察的序列,它是共享状态变化。
1.7.3 使用样例
这个是官方提供的样例,大致的意思是根据一个输入框的关键字,来请求数据,然后将获取到的结果绑定到另一个 Label 和 TableView 中。
-
初学者使用 Observable 序列加 bindTo 绑定来实现这个功能的话可能会这么写:
-
实例1.7.3.1:
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance) //在主线程中操作,0.3秒内值若多次改变,取最后一次
.flatMapLatest { query in //筛选出空值, 拍平序列
fetchAutoCompleteItems(query) //向服务器请求一组结果
}
//将返回的结果绑定到用于显示结果数量的label上
results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)
//将返回的结果绑定到tableView上
results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)
- 上面实例1.7.3.1这个代码存在如下 3 个问题:
- 如果
fetchAutoCompleteItems
的序列产生了一个错误(网络请求失败),这个错误将取消所有绑定。此后用户再输入一个新的关键字时,是无法发起新的网络请求。- 如果
fetchAutoCompleteItems
在后台返回序列,那么刷新页面也会在后台进行,这样就会出现异常崩溃。- 返回的结果被绑定到两个
UI
元素上。那就意味着,每次用户输入一个新的关键字时,就会分别为两个 UI 元素发起 HTTP 请求,这并不是我们想要的结果。
-
把上面几个问题修改后的代码是这样的:
-
实例1.7.3.2:
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance)//在主线程中操作,0.3秒内值若多次改变,取最后一次
.flatMapLatest { query in //筛选出空值, 拍平序列
fetchAutoCompleteItems(query) //向服务器请求一组结果
.observeOn(MainScheduler.instance) //将返回结果切换到到主线程上
.catchErrorJustReturn([]) //错误被处理了,这样至少不会终止整个序列
}
.shareReplay(1) //HTTP 请求是被共享的
//将返回的结果绑定到显示结果数量的label上
results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)
//将返回的结果绑定到tableView上
results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)
- 虽然我们通过增加一些额外的处理,让程序可以正确运行。到对于一个大型的项目来说,如果都这么干也太麻烦了,而且容易遗漏出错。
- 而如果我们使用 Driver 来实现的话就简单了,代码如下:
- 实例1.7.3.3:
let results = query.rx.text.asDriver() // 将普通序列转换为 Driver
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.asDriver(onErrorJustReturn: []) // 仅仅提供发生错误时的备选返回值
}
//将返回的结果绑定到显示结果数量的label上
results
.map { "\($0.count)" }
.drive(resultCount.rx.text) // 这里使用 drive 而不是 bindTo
.disposed(by: disposeBag)
//将返回的结果绑定到tableView上
results
.drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { // 同样使用 drive 而不是 bindTo
(_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)
- 由于 drive 方法只能被 Driver 调用。这意味着,如果代码存在 drive,那么这个序列不会产生错误事件并且一定在主线程监听。这样我们就可以安全的绑定 UI 元素。
- 对上面实例1.7.3.3代码做一下解释:
代码讲解:
- 首先我们使用
asDriver
方法将ControlProperty
转换为Driver
。- 接着我们可以用
.asDriver(onErrorJustReturn: [])
方法将任何Observable
序列都转成Driver
,因为我们知道序列转换为Driver
要他满足 3 个条件:
A.不会产生error
事件
B.一定在主线程监听(MainScheduler
)
C.共享状态变化(shareReplayLatestWhileConnected
)
而asDriver(onErrorJustReturn: [])
相当于以下代码:
let safeSequence = xs
.observeOn(MainScheduler.instance) // 主线程监听
.catchErrorJustReturn(onErrorJustReturn) // 无法产生错误
.share(replay: 1, scope: .whileConnected)// 共享状态变化
return Driver(raw: safeSequence) // 封装- 同时在
Driver
中,框架已经默认帮我们加上了shareReplayLatestWhileConnected
,所以我们也没必要再加上"replay
"相关的语句了。- 最后记得使用
drive
而不是bindTo
1.8 特征序列3:ControlProperty、 ControlEvent
1.8.1 ControlProperty
- 基本介绍
(1)ControlProperty 是专门用来描述 UI 控件属性,拥有该类型的属性都是被观察者(Observable)。
(2)ControlProperty 具有以下特征:
A.不会产生 error 事件
B.一定在 MainScheduler 订阅(主线程订阅)
C.一定在 MainScheduler 监听(主线程监听)
D.共享状态变化
- 实例1.8.1.1
(1)其实在 RxCocoa 下许多 UI 控件属性都是被观察者(可观察序列)。比如我们查看源码(UITextField+Rx.swift
),可以发现 UITextField
的 rx.text
属性类型便是 ControlProperty<String?>
:
import RxSwift
import UIKit
extension Reactive where Base: UITextField {
public var text: ControlProperty<String?> {
return value
}
public var value: ControlProperty<String?> {
return base.rx.controlPropertyWithDefaultEvents(
getter: { textField in
textField.text
},
setter: { textField, value in
if textField.text != value {
textField.text = value
}
}
)
}
//......
}
(2)那么我们如果想让一个 textField 里输入内容实时地显示在另一个 label 上,即前者作为被观察者,后者作为观察者。可以这么写:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label: UILabel!
let disposeBag = DisposeBag()
override func viewDidLoad() {
//将textField输入的文字绑定到label上
textField.rx.text
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
extension UILabel {
public var fontSize: Binder<CGFloat> {
return Binder(self) { label, fontSize in
label.font = UIFont.systemFont(ofSize: fontSize)
}
}
}
1.8.2 ControlEvent
- 基本介绍
(1)ControlEvent 是专门用于描述 UI 所产生的事件,拥有该类型的属性都是被观察者(Observable)。
(2)ControlEvent 和 ControlProperty 一样,都具有以下特征:
A.不会产生 error 事件
B.一定在 MainScheduler 订阅(主线程订阅)
C.一定在 MainScheduler 监听(主线程监听)
D.共享状态变化
- 实例1.8.2.1
- 同样地,在 RxCocoa 下许多 UI 控件的事件方法都是被观察者(可观察序列)。比如我们查看源码(UIButton+Rx.swift),可以发现 UIButton 的 rx.tap 方法类型便是 ControlEvent:
import RxSwift
import UIKit
extension Reactive where Base: UIButton {
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}
- 那么我们如果想实现当一个 button 被点击时,在控制台输出一段文字。即前者作为被观察者,后者作为观察者。可以这么写:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
//订阅按钮点击事件
button.rx.tap
.subscribe(onNext: {
print("欢迎访问hangge.com")
}).disposed(by: disposeBag)
}
}
1.9 给 UIViewController 添加 RxSwift 扩展
- 这里我们对 UIViewController 进行扩展:\
- 将
viewDidLoad
、viewDidAppear
、viewDidLayoutSubviews
等各种ViewController
生命周期的方法转成ControlEvent
方便在RxSwift
项目中使用。- 增加
isVisible
序列属性,方便对视图的显示状态进行订阅。- 增加
isDismissing
序列属性,方便对视图的释放进行订阅。
- UIViewController+Rx.swift代码如下:
import UIKit
import RxCocoa
import RxSwift
public extension Reactive where Base: UIViewController {
public var viewDidLoad: ControlEvent<Void> {
let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in }
return ControlEvent(events: source)
}
public var viewWillAppear: ControlEvent<Bool> {
let source = self.methodInvoked(#selector(Base.viewWillAppear))
.map { $0.first as? Bool ?? false }
return ControlEvent(events: source)
}
public var viewDidAppear: ControlEvent<Bool> {
let source = self.methodInvoked(#selector(Base.viewDidAppear))
.map { $0.first as? Bool ?? false }
return ControlEvent(events: source)
}
public var viewWillDisappear: ControlEvent<Bool> {
let source = self.methodInvoked(#selector(Base.viewWillDisappear))
.map { $0.first as? Bool ?? false }
return ControlEvent(events: source)
}
public var viewDidDisappear: ControlEvent<Bool> {
let source = self.methodInvoked(#selector(Base.viewDidDisappear))
.map { $0.first as? Bool ?? false }
return ControlEvent(events: source)
}
public var viewWillLayoutSubviews: ControlEvent<Void> {
let source = self.methodInvoked(#selector(Base.viewWillLayoutSubviews))
.map { _ in }
return ControlEvent(events: source)
}
public var viewDidLayoutSubviews: ControlEvent<Void> {
let source = self.methodInvoked(#selector(Base.viewDidLayoutSubviews))
.map { _ in }
return ControlEvent(events: source)
}
public var willMoveToParentViewController: ControlEvent<UIViewController?> {
let source = self.methodInvoked(#selector(Base.willMove))
.map { $0.first as? UIViewController }
return ControlEvent(events: source)
}
public var didMoveToParentViewController: ControlEvent<UIViewController?> {
let source = self.methodInvoked(#selector(Base.didMove))
.map { $0.first as? UIViewController }
return ControlEvent(events: source)
}
public var didReceiveMemoryWarning: ControlEvent<Void> {
let source = self.methodInvoked(#selector(Base.didReceiveMemoryWarning))
.map { _ in }
return ControlEvent(events: source)
}
//表示视图是否显示的可观察序列,当VC显示状态改变时会触发
public var isVisible: Observable<Bool> {
let viewDidAppearObservable = self.base.rx.viewDidAppear.map { _ in true }
let viewWillDisappearObservable = self.base.rx.viewWillDisappear
.map { _ in false }
return Observable<Bool>.merge(viewDidAppearObservable,
viewWillDisappearObservable)
}
//表示页面被释放的可观察序列,当VC被dismiss时会触发
public var isDismissing: ControlEvent<Bool> {
let source = self.sentMessage(#selector(Base.dismiss))
.map { $0.first as? Bool ?? false }
return ControlEvent(events: source)
}
}
- 通过扩展,我们可以直接对 VC 的各种方法进行订阅。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//页面显示状态完毕
self.rx.isVisible
.subscribe(onNext: { visible in
print("当前页面显示状态:\(visible)")
}).disposed(by: disposeBag)
//页面加载完毕
self.rx.viewDidLoad
.subscribe(onNext: {
print("viewDidLoad")
}).disposed(by: disposeBag)
//页面将要显示
self.rx.viewWillAppear
.subscribe(onNext: { animated in
print("viewWillAppear")
}).disposed(by: disposeBag)
//页面显示完毕
self.rx.viewDidAppear
.subscribe(onNext: { animated in
print("viewDidAppear")
}).disposed(by: disposeBag)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
运行结果:
1.10 Schedulers
1.10.1 Schedulers简介
- (1)调度器(
Schedulers
)是RxSwift
实现多线程的核心模块,它主要用于控制任务在哪个线程或队列运行。 - (2)
RxSwift
内置了如下几种Scheduler
:
CurrentThreadScheduler
:表示当前线程Scheduler
。(默认使用这个)MainScheduler
:表示主线程。如果我们需要执行一些和 UI 相关的任务,就需要切换到该 Scheduler 运行。SerialDispatchQueueScheduler
:封装了 GCD 的串行队列。如果我们需要执行一些串行任务,可以切换到这个 Scheduler 运行。ConcurrentDispatchQueueScheduler
:封装了 GCD 的并行队列。如果我们需要执行一些并发任务,可以切换到这个Scheduler
运行。OperationQueueScheduler
:封装了NSOperationQueue
。
1.10.2 实例
- 这里以请求网络数据并显示为例。我们在后台发起网络请求,然后解析数据,最后在主线程刷新页面。
- 过去我们使用 GCD 来实现,代码大概是这样的:
//现在后台获取数据
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: url)
//再到主线程显示结果
DispatchQueue.main.async {
self.data = data
}
}
- 如果使用 RxSwift 来实现,代码大概是这样的:
let rxData: Observable<Data> = ...
rxData
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) //后台构建序列
.observeOn(MainScheduler.instance) //主线程监听并处理序列结果
.subscribe(onNext: { [weak self] data in
self?.data = data
})
.disposed(by: disposeBag)
1.10.3 subscribeOn 与 observeOn 区别
- 1)
subscribeOn()
- 该方法决定数据序列的构建函数在哪个
Scheduler
上运行。- 比如上面样例,由于获取数据、解析数据需要花费一段时间的时间,所以通过
subscribeOn
将其切换到后台Scheduler
来执行。这样可以避免主线程被阻塞。
- 2).
observeOn()
- 该方法决定在哪个
Scheduler
上监听这个数据序列。- 比如上面样例,我们获取并解析完毕数据后又通过
observeOn
方法切换到主线程来监听并且处理结果。
收录自|地址