Combineのdebounceを試す

開発環境

> xcodebuild -version
Xcode 13.3
Build version 13E113

モチベーション

debounceを使ったことがないので使ってみたい

debounce

Publishes elements only after a specified time interval elapses between events.
https://developer.apple.com/documentation/combine/publisher/debounce(for:scheduler:options:)

イベント間に指定したインターバルが経過した後にのみ要素をpublishする。

とりあえず公式ドキュメントのコードを動かしてみる。

公式ドキュメントのコード

let bounces:[(Int,TimeInterval)] = [
    (0, 0),
    (1, 0.25),  // 0.25s interval since last index
    (2, 1),     // 0.75s interval since last index
    (3, 1.25),  // 0.25s interval since last index
    (4, 1.5),   // 0.25s interval since last index
    (5, 2)      // 0.5s interval since last index
]

let subject = PassthroughSubject<Int, Never>()
cancellable = subject
    .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
    .sink { index in
        print ("Received index \(index)")
    }

for bounce in bounces {
    DispatchQueue.main.asyncAfter(deadline: .now() + bounce.1) {
        subject.send(bounce.0)
    }
}

// Prints:
//  Received index 1
//  Received index 4
//  Received index 5

コメントを読んで手元で順を追っていったら分かった。 以下の観点が大事っぽい。

  • イベント間に指定したインターバルが経過した後にのみ要素をpublishする
  • sendした時間をs, インターバルをtとすると、[s, s+t) の間に別の値がsendされなければpublishされる
    • [s, s+t] ではない

上記を踏まえて処理を見ていくと、以下のようになる。

  • Time 0: index 0がSend
    • Subscriber側で0.5秒のdebounceが設定されているため、0.5秒までに何もSendされなければ.sinkに到達する
  • Time 0.25: index 1がSend
    • 0.5秒になるまでにSendされたため、index 0は破棄されて、index 1は0.75秒までに何もSendされなければ.sinkに到達する
  • Time: 0.75
    • index 1が.sinkに到達する
  • Time: 1: index 2がSend
    • 同様に1.5(= 1.0 + 0.5)秒までに何もSendされなければ.sinkに到達する
  • Time: 1.25: index 3がSend
    • 1.5秒になるまでにSendされたため、index 2は破棄されて、index 3は1.75(1.25+0.5)秒までに何もSendされなければ.sinkに到達する
  • Time: 1.5: index 4がSend
    • 1.75秒になるまでにSendされたため、index 3は破棄されて、index 4は2.0(1.5+0.5)秒までに何もSendされなければ.sinkに到達する
  • Time 2.0: index 4が.sinkに到達し、index 5がSend
  • Time 2.5: index 5が.sinkに到達する

使い時

  • CoreLocationでGPSデータを取得する時
    • 徒歩などの、ある程度速度が知れていてGPSデータの取得を抑制できるシチュエーション

無理やり一つ出してみたが、大量のイベントをアプリ側で処理する事例が他に思いつかなかった。というのもサーバー側で抑制しないと通信量えらいことになりそう。アプリ内で大量の処理を抑制したい時に使う?例が思いついたら別記事を書く。

参考