iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
Mobile Development

RxSwift / 30天探索之旅系列 第 29

第 29 天 - RxSwiftExt

  • 分享至 

  • xImage
  •  

嗨,今天講講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時,發現有些很實用的作法,對比之前的作法,更加的簡潔,更加的可讀,也跟各位做個分享,明天就是最後一天了,大家明天見。


上一篇
第 28 天 - RxGesture
下一篇
第 30 天 - 總結
系列文
RxSwift / 30天探索之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言