23
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【iOS】RxSwiftでのタイマーとflatMapLatestについて

Last updated at Posted at 2019-04-12

やりたいこと

RxSwiftを使ってタイマーを実現したい。
プロダクトでRxSwiftを導入しているので、Timerクラスを使わずに、RxSwiftのinterval(_ period: RxTimeInterval, scheduler: SchedulerType)で時間を計測しようと思った。

とりあえず実装

UISwitchのON・OFFでタイマーのStopとStartを管理する簡単なSample

class ViewController: UIViewController {
    
    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var timerSwitch: UISwitch!
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        timerSwitch.rx.value
            .flatMap { $0 ? Observable<Int>.interval(0.1, scheduler: MainScheduler.instance) : .empty() }
            .map { String($0) }
            .bind(to: timerLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

スクリーンショット
timer1.gif

結果
うまく動いていない。。。
switchをOFFにしてもtimerが止まらない。かつ、2度目のONで2つのTimerが動いているような挙動

とりあえずデバッグ

各イベントの後にdebug()を仕込む

timerSwitch.rx.value
    .debug("switch")
    .flatMap { $0 ? Observable<Int>.interval(0.1, scheduler: MainScheduler.instance) : .empty() }
    .debug("timer")
    .map { String($0) }
    .bind(to: timerLabel.rx.text)
    .disposed(by: disposeBag)

デバッグ結果

2019-04-12 11:59:02.794: switch -> subscribed
2019-04-12 11:59:02.795: switch -> Event next(false)
2019-04-12 11:59:04.193: switch -> Event next(true) <- Switch On
2019-04-12 11:59:04.293: timer -> Event next(0)
2019-04-12 11:59:04.394: timer -> Event next(1)
2019-04-12 11:59:04.494: timer -> Event next(2)
2019-04-12 11:59:04.593: timer -> Event next(3)
2019-04-12 11:59:04.642: switch -> Event next(false) <- Switch Off
2019-04-12 11:59:04.693: timer -> Event next(4)      <- あれ? 動いている
2019-04-12 11:59:04.794: timer -> Event next(5)
2019-04-12 11:59:04.893: timer -> Event next(6)
2019-04-12 11:59:04.994: timer -> Event next(7)
2019-04-12 11:59:05.248: switch -> Event next(true) <- Switch On
2019-04-12 11:59:05.293: timer -> Event next(8)
2019-04-12 11:59:05.349: timer -> Event next(0)      <- 2つ目のTimerが起動
2019-04-12 11:59:05.394: timer -> Event next(9)
2019-04-12 11:59:05.449: timer -> Event next(1)
2019-04-12 11:59:05.493: timer -> Event next(10)
2019-04-12 11:59:05.550: timer -> Event next(2)

flatMap内で生成したObservable.intervalが破棄されずにイベントを発火し続けている。

flatMapflatMapLatestについて

flatMap

// Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence.
public func flatMap<O>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E> where O : ObservableConvertibleType
flatMap.c.png 新しいObservableを生成して、生成したObservableを1つのObservableにマージする。

上のマーブル図では赤のイベントでObservableが生成されて、次の緑のイベントでもObservableが生成されて、赤と緑のObservableがマージされたObsrvableのイベントが結果流れている。

flatMapLatest

// It is a combination of `map` + `switchLatest` operator
public func flatMapLatest<O>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E> where O : ObservableConvertibleType

→ 簡単に言えば map + switchLatest

ならswitchLatestは?

switch.c.png 新しい内部のObservableを受信するたびに、前の内部のObservableの購読を中止して、最新のObservableを購読する。

flatMapflatMapLatest の違いは?

mergeswitchLatestの違い。

flatMapは新しく生成されたObservableがmergeされて、flatMapLatestではswitchLatestされる。
なので、flatMapの場合はイベントで生成されたObservableがmergeされるので、生成されたObservableがcompletionしない限り増えていくが、flatMapLatestは新しくObservableが生成されると前のObservableの購読を中止するため増えない。

今回のTimerが増えていったのは、flatMapを使っていたためTimerのObservableが次々にmergeされたからである。

改善

flatMapの箇所をflatMapLatestに変えて無事に動きました。

class ViewController: UIViewController {
    
    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var timerSwitch: UISwitch!
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        timerSwitch.rx.value
            .flatMapLatest { $0 ? Observable<Int>.interval(0.1, scheduler: MainScheduler.instance) : .empty() }
            .map { String($0) }
            .bind(to: timerLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

まとめ

  • インクリメンタルサーチなどでUIの更新を抑えるために使われるflatMapLatestの他の使い所
  • Observable.interval()は、Timer`と比べて、インスタンスの管理が楽で使いやすい
  • flatMap, flatMapLatest, switchLatest, mergeの理解

参考

23
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?