嗨,今天講講GitHub - RxSwiftCommunity/RxSwiftExt,再開始IT邦系列之後,才發現了這個library,如同它的名字,就是對RxSwift的Operator進行擴充,多了25種operator、materialize擴充和兩個Reactive Extensions,感覺像是個大禮包(?),今天就針對個人覺得有趣或是可能常用到的提出來介紹。
如果是用CocoaPod,加入pod 'RxSwiftExt', '~> 5'
,若是要使用UIViewPropertyAnimator
跟UITableView
的擴充,需要在安裝pod 'RxSwiftExt/Core'
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
可以把重複的元素去除
Observable.of("a", "1", "b", "1", "c")
.distinct()
.debug("Result")
.subscribe()
.disposed(by: disposeBag)
執行結果
a
1
b
c
相似的operator有distinctUntilChanged
,但distinctUntilChanged
是判斷是否與上一個元素重複,是的話則過濾掉,distinct
則是看全部曾經發送過的元素是否重複。
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)
我們在第 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()
.exponentialDelayed
是RepeatBehavior
枚舉中的一個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
跟retry
極為相似,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()
我們在第 11 天 - Transforming Observables(下) 所提到的 materialized+elements.swift 也被整合進RxSwiftExt中了。
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 }
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
這是對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)
}
}
myView
下移100距離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倍
在第 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時,發現有些很實用的作法,對比之前的作法,更加的簡潔,更加的可讀,也跟各位做個分享,明天就是最後一天了,大家明天見。