第 29 天 - RxSwiftExt

嗨,今天讲讲GitHub - RxSwiftCommunity/RxSwiftExt,再开始IT邦系列之後,才发现了这个library,如同它的名字,就是对RxSwift的Operator进行扩充,多了25种operator、materialize扩充和两个Reactive Extensions,感觉像是个大礼包(?),今天就针对个人觉得有趣或是可能常用到的提出来介绍。

安装

如果是用CocoaPod,加入pod 'RxSwiftExt', '~> 5',若是要使用UIViewPropertyAnimatorUITableView的扩充,需要在安装pod 'RxSwiftExt/Core'

Unwrap

unwrap可以unwrap optional value,并且排除掉nil

Observable.of(1, nil, Int("2"), Int("a"), 3)
    .unwrap()
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

执行结果

1
2
3

原本的话,会是这样写

Observable.of(1, nil, Int("2"), Int("a"), 3)
    .filter { $0 != nil }
    .map { $0! }

相比之下,unwrap更简洁也更加的可读了

Distinct

distinct可以把重复的元素去除

Observable.of("a", "1", "b", "1", "c")
    .distinct()
    .debug("Result")
    .subscribe()
    .disposed(by: disposeBag)

执行结果

a
1
b
c

相似的operator有distinctUntilChanged,但distinctUntilChanged是判断是否与上一个元素重复,是的话则过滤掉,distinct则是看全部曾经发送过的元素是否重复。

Pairwise

pairwise有点像是滑动视窗,把按照顺序两两合并成tuple

Observable.of(1, 2, 3, 4, 5, 6)
    .pairwise()
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

执行结果

(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)

如果要用原本写法,大概会像下面这样

Observable.zip(observable, observable.skip(1)) { ($0, $1) }
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

Retry

我们在第 18 天 - Error Handling Operators (下)那篇,介绍retryWhen时,写了延迟retry范例,在RxSwiftExt中,对此作了封装,让写法更佳的简洁,我们就拿原本retryWhen的范例做修改

let result = subject
    .flatMapLatest { _ in
        API().request().asObservable().debug("Call")
            .retry(.exponentialDelayed(maxCount: 5, initial: 1.0, multiplier: 1.0))
            .materialize()
    }
    .share()

.exponentialDelayedRepeatBehavior枚举中的一个case,你也可以选择只定义次数、固定时间或是自订时间

public enum RepeatBehavior {
	case immediate (maxCount: UInt)
	case delayed (maxCount: UInt, time: Double)
	case exponentialDelayed (maxCount: UInt, initial: Double, multiplier: Double)
	case customTimerDelayed (maxCount: UInt, delayCalculator: (UInt) -> DispatchTimeInterval)
}

.exponentialDelayed则是提供一个延迟时间增加的公式initial * pow(1 + multiplier, Double(currentAttempt - 1)),如果multiplier是1,那延迟时间就是2的0次方、2的1次方、2的2次方,依此类推

RepeatWithBehavior

repeatWithBehaviorretry极为相似,retry是侦测到.error後进行retry,而repeatWithBehavior是侦测到.completed後进行repeat

let result = subject
    .flatMapLatest { _ in
        API().request().asObservable().debug("Call")
            .repeatWithBehavior(.exponentialDelayed(maxCount: 5, initial: 1.0, multiplier: 1.0))
            .materialize()
    }
    .share()

Errors, Elements

我们在第 11 天 - Transforming Observables(下) 所提到的 materialized+elements.swift 也被整合进RxSwiftExt中了。

ofType

ofType可以筛选出元素的type

let result = Observable.of(NSNumber(value: 1),
                           NSDecimalNumber(string: "2"),
                           NSNumber(value: 3),
                           NSNumber(value: 4),
                           NSDecimalNumber(string: "5"),
                           NSNumber(value: 6))
result
    .ofType(NSDecimalNumber.self)
    .subscribe { print($0) }
    .disposed(by: disposeBag)

执行结果

next(2)
next(5)
completed

如果要用原本写法,大概会像下面这样

result
    .filter { $0 is NSDecimalNumber }

Partition

partiion可以帮你做二分流,也就是符合条件的是一个Observable,不符合条件的是另一条Observable

let (evens, odds) = numbers.partition { $0 % 2 == 0 }

evens.debug("even").subscribe().disposed(by: disposeBag)
odds.debug("odds").subscribe().disposed(by: disposeBag)

执行结果如下

even -> subscribed
even -> Event next(2)
even -> Event next(4)
even -> Event next(6)
even -> Event completed
even -> isDisposed
odds -> subscribed
odds -> Event next(1)
odds -> Event next(3)
odds -> Event next(5)
odds -> Event completed
odds -> isDisposed

UIViewPropertyAnimator.animate

这是对RxCocoa进行扩增,所以需要安装pod 'RxSwiftExt/Core'animate提供.subscribe.completed时触发动画

建立UI

let button = UIButton(frame: .zero)
let myView = UIView(frame: .zero)

建立动画

var animator1: UIViewPropertyAnimator!
var animator2: UIViewPropertyAnimator!

private func makeAnimators() {
    // 1
    animator1 = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { [unowned self] in
        self.myView.transform = self.myView.transform.translatedBy(x: 0, y: 100)
    }
    // 2
    animator2 = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { [unowned self] in
        self.myView.transform = self.myView.transform.scaledBy(x: 1.5, y: 1.5)
    }
}
  1. myView下移100距离
  2. myView放大1.5倍

绑定事件

button.rx.tap
    .flatMap {
        self.animator1.rx.animate()
            .andThen(self.animator2.rx.animate(afterDelay: 0.15))
    }
    .subscribe()
    .disposed(by: disposeBag)

当按钮点击myView会先下滑,延迟0.15秒,後放大1.5倍

UIScrollView.reachedBottom

第 20 天 - TableView + Rx 与范例(上) 当中,我们实现Infinite scroll,需要侦测UITableView滑至最底,所以我们写了

func bindViewModel() {
    tableView.rx.setDelegate(self).disposed(by: disposeBag)
    ...
}

extension ProductListViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { ... }
}

RxSwiftExt对这行为作了封装,写法变得更加简洁,於是我们可以改写成,且允许设定offset距离

tableView.rx.reachedBottom(offset: 40)
    .bind(to: viewModel.triggerNextPage)
    .disposed(by: disposeBag)

在研究RxSwiftExt时,发现有些很实用的作法,对比之前的作法,更加的简洁,更加的可读,也跟各位做个分享,明天就是最後一天了,大家明天见。


<<:  Day 28 / DL x RL / RL 不只会打电动?

>>:  iOS Developer Learning Flutter. Lesson27 Map + Location

桌面端 YouTube 影片下载器--〖2022亲测〗

接下来为您介绍 10 款不错的第三方 YouTube 下载软件!让我们来看看哪个软件才是 2022 ...

Day17 - [丰收款] 永丰API虚拟帐户付款与PayToken查询与更新状态

在经过了多日有一天没一天的研究、写Code与写作,今天假日花了一点时间将原本从Jupyter Not...

[Day 30]用Django架构建置专属的LINEBOT吧 - LIFF(III)建立LIFF页面

好了,最後一天了,也没有太多新花样, 今天就来个组合拳吧! 建立LIFF页面 在LIFF的官方API...

Day 12 - Confusion Matrix 混淆矩阵-模型的好坏 (2)

精确率(precision) 召回率(recall) Precision和Recall同时关注的都是...

Day17|【Git】存在 .git 目录里的东西 - Blob 物件与 Tree 物件(上)

Git 有四种 type (类型) 的物件:blob、tree、commit 和 tag。 本篇主要...