RxSwift 操作符使用

57 阅读5分钟

RxSwift的目的是以可观察对象的形式轻松组合异步操作和数据流,并使用一套方法来转换和组合这些异步工作。

KVO观察、异步操作、UI事件和其他数据流都在序列抽象下统一。

Observable何时开始发布其项目序列?

这取决于可观测的。“热门”Observable可能会在创建后立即开始发射项目,因此任何后来订阅该Observable的观察者都可能会在在中间的某个位置开始观察序列。另一方面,一个“冷”可观察器会等到观察者订阅它之后才开始发射项目,因此这样的观察者可以从一开始就看到整个序列。

冷热观察序列对比

热 Obseavables冷 Observables
是序列是序列
无论是否有观察员订阅,都要使用资源(“产生热量”)在观察者订阅之前不使用资源(不要产生热量)。
变量/属性/常量、点击坐标、鼠标坐标、UI控件值、当前时间异步操作、HTTP连接、TCP连接、流
通常含有~N个元素通常包含~1个元素
无论是否有观察者订阅,都会生成序元素只有当观察者订阅时,才会生成序列元素
序列计算资源通常在所有订阅的观察器之间共享序列计算资源通常被分配给每一个观察者
通常有状态通常没状态

检测内存是否泄漏

在 AppDelegate 的 didFinishLaunchingWithOptions 方法中添加如下代码 :

_ = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
                .subscribe(onNext: { _ in
                    print("Resource count \(RxSwift.Resources.total)")
                })

接下来编译时, 会报错发现不了 Resources 属性。 在 Podfile 中添加如下代码

post_install do |installer|
   installer.pods_project.targets.each do |target|
      if target.name == 'RxSwift'
         target.build_configurations.each do |config|
            if config.name == 'Debug'
               config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D', 'TRACE_RESOURCES']
            end
         end
      end
   end
end

如果还报错, 对比 Pods -> Build Settings -> Other Swift Flags

截屏2023-02-02 下午3.25.47.png 如果不是, 添加相应的 -D DEBUG

再次编译成功!

内存是否泄漏,在订阅开始和订阅取消之后, 查看 RxSwift.Resources.total 是否变化

操作符

amb

当有多个 Observales 被添加到 amb 操作符时

  • 如果其中有 Observables 是 error、complete 等事件, 则只发出该 Observables 的事件
  • 如果其中都是正常的发出元素事件, 则只发出第一个 Observables 的元素。
let disposeBag = DisposeBag()

let observalbles1 = Observable.from([20, 40, 60])
let observalbles2 = Observable.from([1, 2, 3])
let observalbles3 = Observable<Int>.never()
let observalbles4 = Observable<Int>.empty()
let observalbles5 = Observable<Int>.error(GenerError.generic)

let sequences = [observalbles1, observalbles2, observalbles3]

Observable.amb(sequences)
    .subscribe { e in
        print("subscription 1 event: \(e)")
    }
    .disposed(by: disposeBag)

buffer

缓存指定数量的元素时才一起发出, 或者在指定时间内发出元素, 超过指定时间, 有几个发几个, 没有就发出空数组。

//每缓存三个元素,则组合起来一起发出
//如果1秒钟内不够3个也会发出(有几个发几个, 一个没有就发送空数组)
subject.buffer(timeSpan: .seconds(1), count: 3, scheduler: MainScheduler.instance)
.subscribe(onNext: {
    print($0)
})
.disposed(by: disposeBag)      

subject.onNext("1")
subject.onNext("2")
subject.onNext("3")
subject.onNext("A")
subject.onNext("B")
subject.onNext("C")

catch

拦截 error 事件, 将替换成一其他元素或者一组元素。

let subjectWithFail = PublishSubject<Int>()
let replaceSubject = PublishSubject<Int>()
subjectWithFail
    .catch({ e in
        print("error = \(e)")
        return replaceSubject
    })
    .subscribe {
        print($0)
    }
    .disposed(by: disposeBag)

subjectWithFail.onNext(1)
subjectWithFail.onError(GenerError.generic)
replaceSubject.onNext(2)

catchAndReturn

拦截error 事件, 转换为一个元素。

let subjectWithFail = PublishSubject<Int>()
subjectWithFail
    .catchAndReturn(2)
    .subscribe {
        print($0)
    }
    .disposed(by: disposeBag)

subjectWithFail.onNext(1)
subjectWithFail.onError(GenerError.generic)

combineLatest

将多个 Observables 组合起来, 当有其中一个 Observables 发出元素, 则组合将发出一个元素, 组合的规则: 拿各个Observables 中最新的元素, 如图:

截屏2023-02-03 下午4.25.15.png

let first = PublishSubject<String>()
let second = PublishSubject<String>()

Observable.combineLatest(first, second) { $0 + $1 }
    .subscribe {
        print($0.element ?? "")
    }
    .disposed(by: disposeBag)

first.onNext("1")
second.onNext("A")
first.onNext("2")
second.onNext("B")
second.onNext("C")
second.onNext("D")
first.onNext("3")
first.onNext("4")
first.onNext("5")

concat

连接两个或多个Observables, 按顺序串联起来, 前一个 Observables 事件发送没完成, 后一个Observables 不会发出元素。

let subject1 = BehaviorSubject(value: "1")
let subject2 = BehaviorSubject(value: "A")

let subject = BehaviorSubject(value: subject1)

subject
    .asObservable()
    .concat()
    .subscribe { print($0) }
    .disposed(by: disposeBag)

subject1.onNext("2")
subject1.onNext("3")
subject.onNext(subject2)
subject2.onNext("I would be ignored")  // subject1 的事件序列没结束, 所以这个会被忽略
subject2.onNext("B")
subject1.onCompleted()
subject2.onNext("C")  // 在接收 C 之前会先接收 B, 因为是 BehaviorSubject

concatMap

对比 concat 多了一个 map 操作, 就是将序列元素转换为其他类型元素。

connect & publish & refCount

其中 publish 是将 Observable 类型 转为 ConnnetableObservable 类型。在被订阅后, 不会发出元素, 直到被应用connect 为止。可以等所有观察者订阅完成之后,再connect。

let intSequence = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .publish()

intSequence
    .subscribe {
        print("suscription 1 \($0)")
    }
    .disposed(by: disposeBag)

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
    intSequence.connect()
        .disposed(by: self.disposeBag)
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4) {
    intSequence
        .subscribe {
            print("suscription 2 \($0)")
        }
        .disposed(by: self.disposeBag)
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4) {
    intSequence
        .subscribe {
            print("suscription 3 \($0)")
        }
        .disposed(by: self.disposeBag)
}
  • publish 将 Observable 转换为 可连接的 Observable
  • refCount 将 可连接的 Observable 转换为 Observable
  • replay(n: Int) 和 publish 的区别是, 会缓存 n 个最新的元素。
Observable.of(1, 2, 3)
        .publish()   
        .refCount()  
        .subscribe(onNext: {
            print($0)
        })
        .disposed(by: disposeBag)

Throttle(节流) & Debounce(防抖)

Throttle

让一个函数调用在规定时间内只能执行一次调用, 不能频繁调用, 影响性能。只有过了规定时间间隔, 才执行下一次函数调用。

例如咖啡机, 按一次按钮之后, 连续再次按动按钮, 不再起作用。直到一杯咖啡接满为止, 再次按动按钮, 才会响应

Debounce

触发函数调用, 在n秒后执行, 如果在n秒之内再次触发函数调用,将重新计算, 再延迟 n 秒之后执行函数。

例如电梯的自动关门动作, 20秒后关闭, 如果20秒内有人进入, 重新计算,直到没人再进入为止。

deferred & delay

deferred

直到订阅才产生序列, 为每一个订阅者创建全新的序列。

let obs = Observable.deferred {
    Observable.of(1,2,3)
}

obs.subscribe { e in
    print(e.element ?? 0)
}
.disposed(by: disposeBag)

delay 将序列的元素, 延迟一段时间后发出, 不是间隔发送一个个的元素, 而是延迟到规定的时间,一次发出所有元素。

let obs = Observable.of(1,2,3)
.delay(.seconds(5), scheduler: MainScheduler.instance)

obs
.subscribe { e in
    print(e.element ?? 0)
}
.disposed(by: disposeBag)

materialize & dematerialize

materialize 将事件转换为元素 dematerialize 将上面的转换过程还原。

do

在实际的编程中,有时我们会串联多个operator对事件序列进行处理,虽然这样写起来很方便,但发生问题调试时就很麻烦了,因为紧密串联在一起的代码让我们很难方便的洞察每一个环节的状态。为此,RxSwift提供了一个类似“旁路”功能的operator:do。它的用法和subscribe类似,只不过事件会“穿过”do,继续发给后续的环节。这样,如果我们怀疑某个串联的环节发生了问题,就可以插入一个do operator进行观察

let first = BehaviorSubject(value: "1")
let subject = BehaviorSubject(value: first)

subject
    .do(onNext: { subject in
        print("subject: \(subject)")
    })
    .flatMap { $0 }
    .do(onNext: { element in
        print("element: \(element)")
    })
    .subscribe { e in
        print(e)
    }
    .disposed(by: disposeBag)

flatMap

当 Observable 的元素本身有其他的 Observables 时, 你可以将所有的子Observables 的元素发出。

let first = BehaviorSubject(value: "1")
let second = BehaviorSubject(value: "A")
let subject = BehaviorSubject(value: first)

subject
    .flatMap { $0 }
    .subscribe { e in
        print(e)
    }
    .disposed(by: disposeBag)

first.onNext("2")
first.onNext("3")
subject.onNext(second)
second.onNext("B")
second.onNext("C")

flatMapLatest

将 Observable 的元素转换为 Observables, 取最新的被转换的那个Observale 下的所有子元素。

let disposeBag = DisposeBag()
let first = BehaviorSubject(value: "1")
let second = BehaviorSubject(value: "A")
let subject = BehaviorSubject(value: first)

subject.asObservable()
        .flatMapLatest { $0 }
        .subscribe(onNext: { print($0) })
        .disposed(by: disposeBag)

first.onNext("2")
subject.onNext(second)
second.onNext("B")
first.onNext("3")  // 旧的 Observables 被忽略掉

From

  • 将一个数组转换为 Observale
  • 将一个可选值转换为 Observable
let optional: Int? = 1
let obs = Observable.from(optional: optional)
obs.subscribe {
    print($0)
}
.disposed(by: disposeBag)

相当于

let optional: Int? = 1
let obs = Observable<Int>.create { observer in
    if let element = optional {
        observer.onNext(element)
    }
    observer.onCompleted()
    return Disposables.create()
}

groupBy

将 Observable 的元素分组后, 再发出。

let nums = [1, 2, 3, 4, 5, 6, 7, 8]
Observable.from(nums)
    .groupBy {
        $0 % 2 == 0 ? "偶数" : "奇数"
    }
    .subscribe { e in
        guard let key = e.element?.key else { return }
        e.element?.asObservable()
            .subscribe { e in
                guard let value = e.element else { return }
                print("key = \(key), value = \(value)")
            }
            .disposed(by: self.disposeBag)
    }
    .disposed(by: disposeBag)

interval

每隔一段时间, 发出一个索引数

ignoreElements

忽略掉所有元素, 只发出error 和 complete 事件。 如果只关注事件什么时候终止, 可以用这个。

merge

将多个 Observable 合并成一个, 当某一个Observable 发出一个元素时, 它也发出这个元素。

let first = PublishSubject<String>()
let second = PublishSubject<String>()

Observable.merge(first, second)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

first.onNext("1")
second.onNext("A")
second.onNext("B")
first.onNext("2")

Never

不会发出任何事件, 注意和 empty(只发出完成事件) 的区分。

observeOn & su subscribeOn

  • observeOn 指定 Observable 在那个Scheduler发出通知。
  • subscribeOn 指定 Observable 在那个 Scheduler执行。

注意⚠️, 一旦产生 error 事件, 不会等待之前发出的元素接收完毕, 这意味着 error 事件会比其他事件提前被订阅者=接收到。

截屏2023-02-06 上午10.53.19.png

reduce & scan

  • reduce 将所有的元素应用某一个函数, 算出最终结果后发出元素,只发出一个元素
Observable.of(1, 2, 3)
.reduce(0) { result, value in
    result + value
}
// .reduce(0, accumulator: +)
.subscribe(onNext: {
    print($0)
})
.disposed(by: disposeBag)
// 6
  • scan 将所有的元素应用某一个函数, 将每一次的计算结果都输出,发出元素个数不变
Observable.of(1, 2, 3)
.scan(0) { $0 + $1 }
// .scan(0, accumulator: +)
.subscribe(onNext: {
    print($0)
})
.disposed(by: disposeBag)

repeatElement

无止境的发出同一个元素。

Observable.repeatElement(1)
.subscribe(onNext: {
    print($0)
})
.disposed(by: disposeBag)

retry

产生错误时, 将不会发送error 事件, 而是将重新生成序列再发送元素, 这就可能导致, 错误产生之前的元素, 重复发送一次。

var count = 1
let obsWithError = Observable<String>.create { observer in
    observer.onNext("A")
    observer.onNext("B")
    observer.onNext("C")

    if count == 1 {
        print("error produce")
        observer.onError(GenerError.generic)
        count += 1
    }

    observer.onNext("E")
    observer.onNext("F")
    observer.onNext("G")
    return Disposables.create()
}

obsWithError
.retry()
.subscribe(onNext: {
    print($0)
})
.disposed(by: disposeBag)

sample

对 Observable 取样, 通过第二个 Observable 发出元素的时机对 源 Observable 取样。

let orignal = PublishSubject<Int>()
let second = PublishSubject<String>()

orignal.sample(second, defaultValue: 0)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

second.onNext("A")
orignal.onNext(1)
orignal.onNext(2)
second.onNext("B")

// 0 2

shareReplay

使观察着共享 Obsevervable, 会接收到最新的元素。即使是在订阅前产生的。buffSize 缓存个数

skipUntil & takeUntil

  • skipUntil 跳过头几个元素, 直到另一个 Observable发出元素。
let sourceObs = PublishSubject<String>()
let referenceObs = PublishSubject<Int>()        

sourceObs
    .skip(until: referenceObs)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

sourceObs.onNext("A")
sourceObs.onNext("B")
referenceObs.onNext(1)
referenceObs.onNext(2)
sourceObs.onNext("C")
// C
  • takeUntil 发出元素, 直到另一个Observable 发出元素, 后面的将忽略。

skipWhile & takeWhile

  • skipWhile 跳过头几个元素, 直到判定条件为 false
  • takeWhile 取头几个元素, 直到判定条件为 false

timeout

在固定时间内没有发出任何元素, 将产生 error 事件

let sourceObs = PublishSubject<String>()
let referenceObs = PublishSubject<Int>()
sourceObs
    .timeout(.seconds(2), scheduler: MainScheduler.instance)
    .subscribe {
        print($0)
    }
    .disposed(by: disposeBag)

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    sourceObs.onNext("A") // 发出
}

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    sourceObs.onNext("B"// 超时, 不发出
}

带 other 参数的

如果下一个元素没有在从其前一个元素开始的指定超时持续时间内接收到,则使用另一个可观察序列从那时起生成未来消息。

let sourceObs = PublishSubject<String>()
let referenceObs = PublishSubject<String>()

sourceObs
    .timeout(.seconds(2), other: referenceObs, scheduler: MainScheduler.instance)
    .subscribe {
        print($0)
    }
    .disposed(by: disposeBag)

sourceObs.onNext("A")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    referenceObs.onNext("1")
    referenceObs.onNext("2")
}

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    sourceObs.onNext("B"// 不会发出, 已经被 referenceObs 替代
}

timer

在延迟一段时间后, 按间隔时间, 产生索引数

let rxTimer = Observable<Int>
            .timer(.seconds(2), period: .seconds(1), scheduler: MainScheduler.instance)
// 2 秒后,每1秒产生索引数,period 不传的时候, 只产生索引0

let timerDisposeable = rxTimer
    .subscribe {
        print($0)
    }
//            .disposed(by: disposeBag)  // 自动释放

DispatchQueue.main.asyncAfter(deadline: .now() + 8) {
    timerDisposeable.dispose() // 手动暂停 timer
}

using

创建一个 Observable的 同时, 可以创建一个可被清除的资源。 Observable 事件终止时, 资源被清除。

_ = Observable<Int>.using({ () -> MyDisposable in
    let dispose = MyDisposable()
    print("创建source: \(dispose)")
    return dispose
}, observableFactory: {
    factory in
    print("创建factory: \(factory)")
    return Observable.of(1,2,3,4,5)
        .debug("factory")
})
    .debug("using")
    .subscribe()
            
class MyDisposable: NSObject, Disposable {
    func dispose() {
        print("MyDisposable --- \(#function)")
    }
    
    deinit {
        print("MyDisposable --- \(#function)")
    }
}

window

buffer 周期性的将缓存的元素集合发送出来,而 window 周期性的将元素集合以 Observable 的形态发送出来。

buffer 要等到元素搜集完毕后,才会发出元素序列。而 window 可以实时发出元素序列。

Observable.of(1,2,3,4,5,6)
    .window(timeSpan: .seconds(2), count: 2, scheduler: MainScheduler.instance)
    .subscribe {
        guard let interObs = $0.element?.asObservable() else { return }
        interObs
            .subscribe(onNext: {
                print($0)
            })
            .disposed(by: self.disposeBag)
    }
    .disposed(by: disposeBag)

withLatestFrom

将两个 Observable 组合起来, 当第一个Observable 发出元素的时候, 取的是第二个Observable 最新的元素。

firstSubject
     .withLatestFrom(secondSubject)
     .subscribe(onNext: { print($0) })
     .disposed(by: disposeBag)


firstSubject.onNext("🅰️")
firstSubject.onNext("🅱️")
secondSubject.onNext("1")
secondSubject.onNext("2")
firstSubject.onNext("🆎")
// 2

zip

将多个 Observable 组合起来, 严格按照每个序列的索引组合。

let obs1 = Observable.of("1", "2", "3")
let obs2 = Observable.of("A", "B")

Observable.zip(obs1, obs2) { $0 + $1 }
    .subscribe(onNext: { e in
        print(e)
    })
    .disposed(by: disposeBag)

参考链接:

github 链接

RxSwift 中文文档