RxSwift学习:基础使用篇-高阶函数

我们知道Swift中有很多高阶函数,非常好用,而且效率都很高,如我们经常使用的map,fliter,flatmap等等。详情可以参考我之前的一篇文章:Swift的高阶函数

Swift书籍资料下载:下载地址

1. 高阶函数

1.1 算数、以及聚合操作(Mathematical and Aggregate Operators)

1.1.1 toArray

  • 该操作符先把一个序列转成一个数组,并作为一个单一的事件发送,然后结束。

image.png

  • 实例1.1.1
let disposeBag = DisposeBag()
 
Observable.of(1, 2, 3)
    .toArray()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

运行结果:

image.png

1.1.2 reduce

  • 基本介绍
  1. reduce 接受一个初始值,和一个操作符号。
  2. reduce 将给定的初始值,与序列里的每个值进行累计运算。得到一个最终结果,并将其作为单个值发送出去。

image.png

  • 实例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

  • 基本介绍
  1. concat 会把多个 Observable 序列合并(串联)为一个 Observable 序列。
  2. 并且只有当前面一个 Observable 序列发出了 completed 事件,才会开始发送下一个 Observable 序列事件。

image.png

  • 实例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)

运行结果:

image.png

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 之后才会开始。

image.png

  • 实例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

  • 基本介绍
  1. replay 同上面的 publish 方法相同之处在于:会将将一个正常的序列转换成一个可连接的序列。同时该序列不会立刻发送事件,只有在调用 connect 之后才会开始。
  2. replaypublish 不同在于:新的订阅者还能接收到订阅之前的事件消息(数量由设置的 bufferSize 决定)。

image.png

  • 实例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

  • 基本介绍
  1. multicast 方法同样是将一个正常的序列转换成一个可连接的序列。
  2. 同时 multicast 方法还可以传入一个 Subject,每当序列发送事件时都会触发这个 Subject 的发送。

image.png

  • 实例 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)") })
}

运行结果:

image.png

1.2.5 refCount

  • 基本介绍
  1. refCount 操作符可以将可被连接的 Observable 转换为普通 Observable
  2. 即该操作符可以自动连接和断开可连接的 Observable。当第一个观察者对可连接的 Observable 订阅时,那么底层的 Observable 将被自动连接。当最后一个观察者离开时,那么底层的 Observable 将被自动断开连接。

image.png

  • 实例 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)") })
}

运行结果: image.png

1.2.6 share(replay:)

  • 基本介绍
  1. 该操作符将使得观察者共享源 Observable,并且缓存最新的 n 个元素,将这些元素直接发送给新的观察者。
  2. 简单来说 shareReplay 就是 replayrefCount 的组合。
  • 实例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 的所有元素都先拖延一段设定好的时间,然后才将它们发送出来。

image.png

  • 实例 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 进行订阅操作。

image.png

  • 实例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

  • 基本介绍
  1. 该操作符可以将序列产生的事件,转换成元素。
    通常一个有限的Observable将产生零个或者多个 onNext 事件,
  2. 最后产生一个 onCompleted 或者onError事件。而 materialize 操作符会将 Observable 产生的这些事件全部转换成元素,然后发送出来。

image.png

  • 实例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 转换后的元素还原。

image.png

  • 实例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 事件。

timeout

  • 实例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 终止了,那么这个资源就会被清除掉了。

using

  • 实例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

  • 基本介绍
  1. 该方法可以捕获 error,并对其进行处理。
  2. 同时还能返回另一个 Observable 序列进行订阅(切换到新的序列)。

catchError

  • 实例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

  • 基本介绍
  1. 使用该方法当遇到错误的时候,会重新订阅该序列。比如遇到网络请求失败时,可以进行重新连接。
  2. retry() 方法可以传入数字表示重试次数。不传的话只会重试一次。

image.png

  • 实例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)

运行结果

image.png

1.6 特征序列1:Single、Completable、Maybe

  • 除了 Observable,RxSwift 还为我们提供了一些特征序列(Traits):SingleCompletableMaybeDriverControlEvent

1.6.1 Single

  • 基本介绍
  1. Single 是 Observable 的另外一个版本。但它不像 Observable 可以发出多个元素,它要么只能发出一个元素,要么产生一个 error 事件。
  2. 发出一个元素,或一个 error 事件
  3. 不会共享状态变化
  4. 应用场景: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)

运行结果:

image.png

1.6.3 Completable

  • 基本介绍:
  1. CompletableObservable 的另外一个版本。不像 Observable 可以发出多个元素,它要么只能产生一个 completed 事件,要么产生一个 error 事件。
  2. 不会发出任何元素
  3. 只会发出一个 completed 事件或者一个 error 事件
  4. 不会共享状态变化
  • 应用场景:

CompletableObservable<Void> 有点类似。适用于那些只关心任务是否完成,而不需要在意任务返回值的情况。比如:在程序退出时将一些数据缓存到本地文件,供下次启动时加载。像这种情况我们只关心缓存是否成功。

  • CompletableEvent:

为方便使用,RxSwift 为 Completable 订阅提供了一个枚举(CompletableEvent):

  1. .completed:用于产生完成事件
  2. .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)运行结果如下(失败的情况):

image.png

1.6.4 Maybe

  • 基本介绍
  1. 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)

运行结果如下:

image.png

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 个问题:
  1. 如果 fetchAutoCompleteItems 的序列产生了一个错误(网络请求失败),这个错误将取消所有绑定。此后用户再输入一个新的关键字时,是无法发起新的网络请求。
  2. 如果 fetchAutoCompleteItems 在后台返回序列,那么刷新页面也会在后台进行,这样就会出现异常崩溃。
  3. 返回的结果被绑定到两个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代码做一下解释:

代码讲解:

  1. 首先我们使用 asDriver 方法将 ControlProperty 转换为 Driver
  2. 接着我们可以用 .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) // 封装
  3. 同时在 Driver 中,框架已经默认帮我们加上了 shareReplayLatestWhileConnected,所以我们也没必要再加上"replay"相关的语句了。
  4. 最后记得使用 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),可以发现 UITextFieldrx.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
  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)
    }
}
  1. 那么我们如果想实现当一个 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 进行扩展:\
  1. viewDidLoadviewDidAppearviewDidLayoutSubviews 等各种 ViewController 生命周期的方法转成 ControlEvent 方便在 RxSwift 项目中使用。
  2. 增加 isVisible 序列属性,方便对视图的显示状态进行订阅。
  3. 增加 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()
    }
}

运行结果:

image.png

1.10 Schedulers

1.10.1 Schedulers简介

  • (1)调度器(Schedulers)是 RxSwift 实现多线程的核心模块,它主要用于控制任务在哪个线程或队列运行。
  • (2)RxSwift 内置了如下几种 Scheduler
  1. CurrentThreadScheduler:表示当前线程 Scheduler。(默认使用这个)
  2. MainScheduler:表示主线程。如果我们需要执行一些和 UI 相关的任务,就需要切换到该 Scheduler 运行。
  3. SerialDispatchQueueScheduler:封装了 GCD 的串行队列。如果我们需要执行一些串行任务,可以切换到这个 Scheduler 运行。
  4. ConcurrentDispatchQueueScheduler:封装了 GCD 的并行队列。如果我们需要执行一些并发任务,可以切换到这个 Scheduler 运行。
  5. OperationQueueScheduler:封装了 NSOperationQueue

1.10.2 实例

  • 这里以请求网络数据并显示为例。我们在后台发起网络请求,然后解析数据,最后在主线程刷新页面。

image.png

  • 过去我们使用 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()
  1. 该方法决定数据序列的构建函数在哪个 Scheduler 上运行。
  2. 比如上面样例,由于获取数据、解析数据需要花费一段时间的时间,所以通过 subscribeOn 将其切换到后台 Scheduler 来执行。这样可以避免主线程被阻塞。
  • 2). observeOn()
  1. 该方法决定在哪个Scheduler上监听这个数据序列。
  2. 比如上面样例,我们获取并解析完毕数据后又通过 observeOn 方法切换到主线程来监听并且处理结果。

收录自|地址

Swift书籍资料下载:下载地址

猜你喜欢

转载自juejin.im/post/7020784948160233486
今日推荐