Timeโ€‘Based

โฑ๏ธ Timeโ€‘Based Operators โ€” debounce, throttle, timeout ์™ธ

โ€œ์ด๋ฒคํŠธ๋ฅผ ์‹œ๊ฐ„ ์ถ•์œผ๋กœ ๋‹ค๋“ฌ์ž.โ€

์‹œ๊ฐ„ ๊ธฐ๋ฐ˜(Timeโ€‘Based) ์˜คํผ๋ ˆ์ดํ„ฐ๋Š” Observable์˜ ๋ฐœํ–‰ ๊ฐ„๊ฒฉ๊ณผ ์ง€์—ฐ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. UI ์ž…๋ ฅ ์ œ์–ด, ์š”์ฒญ ํญ์ฃผ ๋ฐฉ์ง€, ๋„คํŠธ์›Œํฌ ํƒ€์ž„์•„์›ƒ ๋“ฑ์— ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.


1๏ธโƒฃ ํ•ต์‹ฌ ์˜คํผ๋ ˆ์ดํ„ฐ ๋น„๊ตํ‘œ

Operator
๋™์ž‘
์ฃผ์š” ํŒŒ๋ผ๋ฏธํ„ฐ
๋Œ€ํ‘œ ์‚ฌ์šฉ ์‚ฌ๋ก€

debounce

์ง€์ • ์‹œ๊ฐ„ ๋™์•ˆ ์ƒˆ ์ด๋ฒคํŠธ ์—†์„ ๋•Œ ๋งˆ์ง€๋ง‰ ๊ฐ’ ๋ฐฉ์ถœ

dueTime, scheduler

๊ฒ€์ƒ‰์ฐฝ ์ž๋™์™„์„ฑ, ํ…์ŠคํŠธ ์ž…๋ ฅ

throttle

์ฒซ/๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ๋งŒ ํ†ต๊ณผ (์˜ต์…˜) โ€” ๊ธฐ๊ฐ„ ๋‚ด ์ค‘๋ณต ์–ต์ œ

dueTime, latest

๋ฒ„ํŠผ ์—ฐํƒ€ ๋ฐฉ์ง€, API Rateโ€‘limit

timeout

์ง€์ • ์‹œ๊ฐ„ ๋‚ด onNext ์—†์œผ๋ฉด ์—๋Ÿฌ

dueTime, scheduler, other

์„œ๋ฒ„ ์‘๋‹ต ๋Œ€๊ธฐ ์ œํ•œ

delay

๊ฐ ์ด๋ฒคํŠธ๋ฅผ ์ง€์ • ์‹œ๊ฐ„ ์ง€์—ฐ ํ›„ ๋ฐฉ์ถœ

dueTime, scheduler

์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œํ€€์Šค, ์•Œ๋ฆผ ์ง€์—ฐ

sample

ํŠธ๋ฆฌ๊ฑฐ ์ŠคํŠธ๋ฆผ/์ฃผ๊ธฐ๋งˆ๋‹ค ์ตœ์‹  ๊ฐ’์„ ์ƒ˜ํ”Œ

sampler

์ฃผ์‹ ์ฐจํŠธ Snapshot

buffer

์ผ์ • ๊ธฐ๊ฐ„/๊ฐœ์ˆ˜๋กœ ๋ฐฐ์น˜ ๋ฌถ๊ธฐ

timeSpan, count

์„ผ์„œ Batch Upload


2๏ธโƒฃ ์‹ค์ „ ์Šค๋‹ˆํŽซ

A. debounce โ€” ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ

searchBar.rx.text.orEmpty
    .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest(api.search)
    .bind(to: results)

300โ€ฏms ๋‚ด ์ถ”๊ฐ€ ์ž…๋ ฅ์ด ์—†์„ ๋•Œ๋งŒ ํ˜ธ์ถœ โ†’ API ํŠธ๋ž˜ํ”ฝ ์ ˆ๊ฐ

B. throttle โ€” ์ข‹์•„์š” ๋ฒ„ํŠผ ์ค‘๋ณต ๋ฐฉ์ง€

likeButton.rx.tap
    .throttle(.seconds(1), scheduler: MainScheduler.instance)
    .subscribe(onNext: viewModel.like)

1โ€ฏ์ดˆ ์•ˆ์— ์—ฌ๋Ÿฌ ํƒญ ์ค‘ ์ฒซ ํƒญ๋งŒ ์ฒ˜๋ฆฌ (latest: false)

C. timeout โ€” ์„œ๋ฒ„ ์‘๋‹ต ์‹คํŒจ ์ฒ˜๋ฆฌ

api.fetchData()
    .timeout(.seconds(5), scheduler: MainScheduler.instance)
    .catchError { _ in Observable.just(.timeoutPlaceholder) }
    .bind(to: viewModel.state)

D. buffer โ€” ์„ผ์„œ 10๊ฐœ ์ด๋ฒคํŠธ ๋ฌถ์–ด ์—…๋กœ๋“œ

sensorStream
    .buffer(timeSpan: .seconds(2), count: 10, scheduler: SerialDispatchQueueScheduler(qos: .utility))
    .filter { !$0.isEmpty }
    .flatMap(uploadBatch)
    .subscribe()

3๏ธโƒฃ debounce vs throttle ์‹œ๊ฐ ๋น„๊ต

์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ

โ”€โ”€xโ”€xโ”€โ”€xโ”€xโ”€โ”€โ”€โ”€xโ”€โ”€

debounce(300โ€ฏms)

โ”€โ”€โ”€โ”€โ”€xโ”€โ”€โ”€โ”€โ”€โ”€โ”€xโ”€ (๋งˆ์ง€๋ง‰ ๊ฐ’)

throttle(300โ€ฏms, latest:false)

โ”€โ”€xโ”€โ”€โ”€โ”€โ”€xโ”€โ”€โ”€โ”€โ”€x (์ฒซ ๊ฐ’)

throttle(300โ€ฏms, latest:true)

โ”€โ”€xโ”€โ”€โ”€โ”€โ”€โ”€โ”€xโ”€โ”€โ”€โ”€x (์ฒซ+๋งˆ์ง€๋ง‰)

latest ํ”Œ๋ž˜๊ทธ๊ฐ€ true๋ฉด ๊ธฐ๊ฐ„ ๋์— ์ถ”๊ฐ€๋กœ ์ตœ์‹  ๊ฐ’ ์ „๋‹ฌ.


4๏ธโƒฃ Scheduler & Testing Tips

  • ๋Œ€๋ถ€๋ถ„์˜ ์‹œ๊ฐ„ ์—ฐ์‚ฐ์€ Scheduler ์˜์กด โ†’ ํ•„์š” ์‹œ TestScheduler๋กœ ๊ฐ€์ƒ ์‹œ๊ฐ„ ํ…Œ์ŠคํŠธ (advanceTo).

  • UI ์ž‘์—…์€ MainScheduler, ๋ฐฑ๊ทธ๋ผ์šด๋“œ Polling์€ ConcurrentDispatchQueueScheduler.


5๏ธโƒฃ Edge Cases & pitfalls

  1. debounce 0โ€ฏms ๋Š” ์˜๋ฏธ ์—†๊ณ , ํ”Œ๋žซํผ๋งˆ๋‹ค ์ตœ์†Œ ํ•ด์ƒ๋„(์ฃผ๋กœ 1โ€ฏms).

  2. timeout ๋’ค retry ์—ฐ์† ์‚ฌ์šฉ ์‹œ Cartesian ํญ๋ฐœ ์กฐ์‹ฌ โ€” ์ง€์ˆ˜ Backโ€‘off ํ•„์š”.

  3. buffer์— ํฐ count + ๊ธด timeSpan ์„ค์ •ํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ฒ„ํผ ์ฆ๊ฐ€ ์œ„ํ—˜.


6๏ธโƒฃ Mini Quiz

  1. throttle์˜ latest ์˜ต์…˜์„ true๋กœ ํ•˜๋ฉด ์–ด๋–ค ๊ฐ’์ด ์ถ”๊ฐ€๋กœ ๋ฐฉ์ถœ๋˜๋Š”๊ฐ€?

  2. debounce์™€ sample์˜ ์ฐจ์ด๋ฅผ ๊ฐ„๋‹จํžˆ?

  3. timeout ํ›„ ๋‹ค๋ฅธ Observable๋กœ ๋Œ€์ฒดํ•˜๋ ค๋ฉด ๋ฌด์—‡์„ ์‚ฌ์šฉ?

Answers
  1. ๊ธฐ๊ฐ„ ์ข…๋ฃŒ ์‹œ์ ์˜ ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€๋กœ ์ „๋‹ฌํ•œ๋‹ค.

  2. debounce๋Š” ์ž…๋ ฅ ์ŠคํŠธ๋ฆผ ์ž์ฒด์— ์ง€์—ฐ์„ ์ฃผ์–ด ๋งˆ์ง€๋ง‰ ๊ฐ’๋งŒ, sample์€ ๋ณ„๋„ ํŠธ๋ฆฌ๊ฑฐ์—์„œ ์ตœ์‹  ๊ฐ’์„ ์ƒ˜ํ”Œ๋ง.

  3. timeout์˜ other ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€์ฒด Observable ์ง€์ • or .catchError { _ in other } ์—ฐ์‚ฐ.


์ด์–ด์„œ โ–ถ๏ธ errorHandling ๋กœ ์ด๋™ํ•ด catchError, retry๋กœ ์•ˆ์ •์ ์ธ ์ŠคํŠธ๋ฆผ ๋งŒ๋“ค๊ธฐ๋ฅผ ๋ฐฐ์›Œ๋ด…์‹œ๋‹ค. ๐Ÿš€

Last updated