RxSwift

RxSwfit - Debounce & Throttle

GREEN.1229 2021. 10. 2. 10:29

안녕하세요. 그린입니다🟢

이번 포스팅에서는 RxSwift에서 사용되는 debounce와 throttle에 대해 학습해보겠습니다🧑🏻‍💻

 

자세히 학습하기전 간단하게 debounce와 throttle이 어떤걸 해주는 메서드인가요?

Rx에서는 사용자의 액션이나 정의해준것에 따라 observable이 방출됩니다.
그 소리는 만약 동시다발적으로 여러 액션을 취해주면 observable이 따닥따닥 붙어서 방출되겠죠?
debounce는 간략히 타이머라고 생각하면 됩니다.
타이머를 두고 일정 시간 후 마지막에 들어온 observable을 방출해줍니다.
throttle도 비슷한듯 다른데요.
throttle은 이벤트가 방출되면 타이머를 두고 해당 타이머 이후에 이벤트를 또 다시 방출하는 차이입니다.

그러면 이제 본격적으로 두 메서드를 알아보도록 하죠!

Debounce

debounce는 위에서 말했듯이 타이머를 두고 해당 타이머 이후에 최근 들어온 이벤트를 방출하는 메서드입니다.

요런 형태의 그림일 수 있겠어요.

풀이해보면 1번의 이벤트가 발생하고 debounce를 만나면 특정 시간 x동안 대기 후 방출되게 됩니다.

즉 위 발생한 1번과 방출된 1번 사이의 x라는 시간텀이 존재합니다.

 

그럼 RxSwift 라이브러리에서 debounce가 정의된 구현을 간단히 까볼까요?

extension ObservableType {
    ...
    public func debounce(_ dueTime: RxTimeInterval, scheduler: SchedulerType)
        -> Observable<Element> {
            return Debounce(source: self.asObservable(), dueTime: dueTime, scheduler: scheduler)
    }
}

debounce 부분만 보자면 ObservableType을 익스텐션해서 그 안에 메서드를 정의해줬어요.

그래서 observable에서 해당 메서드를 바로 호출해 사용할 수 있었어요.파라미터 시그니쳐로 dueTime과 scheduler를 받네요!?타이머니까 아무래도 체크해줄 타이머 시간과 어디서 실행해줄 지 스케쥴러가 필요하겠네요!그렇게되면 source는 호출한 observable이 될테고 Debounce 타입을 리턴해주네요.그러면 Debounce 객체를 생성해 그 안에서 또 DebounceSink 객체에도 접근하고 좀 복잡한데 이건 제가 설명을 잘 못하겠어서..!하단 참고자료의 RxSwift 공식문서를 보는게 나을것 같아요👍🏻

 

구현도 까봤겠다 그럼 한번 간단히 만들어서 동작을 확인해볼까요?

제가 RxSwift를 가지고 만들어본 계산기 프로젝트를 예제로 삼아 해보겠씁니다😁

numberButtons.enumerated().forEach({ (button) in
  button.element.rx.tap
  .debounce(.seconds(2), scheduler: MainScheduler.asyncInstance)
  .map { .inputNumber("\(button.offset)") }
  .bind(to: reactor.action)
  .disposed(by: disposeBag)
})

숫자 버튼을 누르면 꼭 2초 타이머를 만들어 실행시켜줍니다.

만약 이럴경우 동일한 숫자를 연달아 누르게 되면 마지막에 방출된 하나의 이벤트만 인식하겠죠!?

아래와 같이 말이죠🧐

debounce

그럼 이런 debounce는 언제 사용될까요?

음.. 다양한 경우가 있을거고 목적에 맞게 사용되야할것 같은 당연한 소리를 하겠지만!?

좀 생각해보자면 사용자가 광클하는 경우 방지할 수 있을것 같아요.

또한 가장 많이 대표적으로 사용되는곳은 자동완성이나 연관검색어 완성이 될 수 있겠죠.

마지막에 들어온 이벤트만 방출해야하니까요!

매번 들어오고있을때마다 방출한다면 과부하가 걸릴것 같아요.

 

Throttle

throttle도 위에서 말했듯이 이벤트가 방출되면 타이머를 두고 타이머 이후에 가장 마지막 최근에 들어온 이벤트를 방출하던가 하지않던가를 동작해주는 메서드입니다.

여기서 latest 파라미터 옵션값을 통해 마지막 최근 이벤트를 방출할지 아니면 그냥 무시할지 정할 수 있습니다.

그건 바로 밑에서 자세히 알아볼께요!

Throttle 에밋되는 그림은 공식문서에 없어서 한번 그려봤어요.. 똥손이지만..

무튼 위 경우는 우선 latest 파라미터가 true일 경우입니다.

일단 이 옵션은 무시하고 흐름을 봐주세요.

1번 이벤트가 생성되면 방출되고 throttle이 동작하여 dueTime 동안 이벤트를 방출하지 않습니다.

해당 정해진 시간이 끝나면 가장 마지막에 들어온 이벤트를 방출해줍니다.

 

그럼 여기도 Throttle의 정의 구현을 간단히 까볼까요?

extension ObservableType {
    ...
    public func throttle(_ dueTime: RxTimeInterval, latest: Bool = true, scheduler: SchedulerType)
        -> Observable<Element> {
        Throttle(source: self.asObservable(), dueTime: dueTime, latest: latest, scheduler: scheduler)
    }
}

Debounce와 그렇게 다를게 없죠? 그치만 내부 객체 구현은 다르다는거..!

파라미터로 다 동일하고 latest가 추가되었어요.

이니셜값은 true인걸 볼 수 있어요ㅎㅎ

여기도 그냥 간단히 메서드의 정의 구현만 소개한거지 그 안에서의 동작은 공식 문서나 아니면 RxSwift 라이브러리 코드를 까보시는걸

추천드립니다👍🏻

 

그럼 여기서 latest 옵션이 대체 뭔지 알아봅시다!

latest는 쉽습니다.

throttle이 동작하고 dueTime이 흐르고 끝나는 시점에서 가장 마지막 최근에 들어온 이벤트를 방출해주느냐 마느냐의 차이입니다🙋🏻‍♂️

기본값은 true로 이벤트를 방출해주죠?

그럼 false일 경우는 어떤 이벤트 흐름이 될까요?

짜잔..! 요렇게 3과 5의 이벤트 방출이 없습니다🤓

 

그럼 프로젝트 예제를 통해 구현해볼까요?

// latest: true
numberButtons.enumerated().forEach({ (button) in
  button.element.rx.tap
  .throttle(.seconds(2), latest: true, scheduler: MainScheduler.ayncInstance)
  .map { .inputNumber("\(button.offset)") }
  .bind(to: reactor.action)
  .disposed(by: disposeBag)
})

// latest: false
numberButtons.enumerated().forEach({ (button) in
  button.element.rx.tap
  .throttle(.seconds(2), latest: false, scheduler: MainScheduler.ayncInstance)
  .map { .inputNumber("\(button.offset)") }
  .bind(to: reactor.action)
  .disposed(by: disposeBag)
})

그럼 아래 시연영상으로 차이를 확인해보시죠!

latest: true
latest: false

차이가 보이시나요🧐

latest가 true이면 2초가 지난 후 여러번 클릭한것중 마지막 5라는 숫자가 쓰여집니다.

반대로 false이면 아무리 클릭해도 2초동안 발생한 이벤트에 대해선 방출되지 않죠!

(55가 된것은 2초 후 5를 클릭한 액션에 대한 방출입니다!)

 

throttle은 어떤 경우에 구현되어 사용될 수 있을까요?

주로 API 요청을 너무 자주하거나 아니면 스크롤등을 내려 수많은 데이터를 계속 받을때 사용됩니다.

왜냐면 어떤 액션을 취해 API 요청을 한다거나 스크롤을 겁나 빠르게 내리면서 수많은 데이터를 받아야한다면

아마 throttle이 없다면 따라가지 못할것 같습니다.

적절히 이벤트들에 block을 걸어줌으로 안정성을 가져갈 수 있을것 같아요🙋🏻‍♂️

또 예제에서 보여준 버튼을 탭하는 경우에도 사용될 수 있겠죠!?

물론 버튼 탭의 dueTime은 줄여야되겠지만요!

자, 이렇게 간단?하게 debounce와 throttle에 대해 알아봤습니다.
주로 여러 이벤트들을 머지하여 합친다음 사용될 때도 있더라구요!
이벤트들을 잘 조합하고 어떤 이벤트를 어떻게 방출할지가 Rx의 관건처럼 느껴졌습니다 개인적으로😃
모두 Rx의 세계에 오신걸 환영합니다🙌

[참고자료]

http://reactivex.io/documentation/operators/debounce.html

 

ReactiveX - Debounce operator

The first variant — called either debounce or throttleWithTimeout — accepts as its parameter a duration, defined as an integer number of milliseconds, and it suppresses any emitted items that are followed by other emitted items during that duration sin

reactivex.io

https://github.com/ReactiveX/RxSwift/blob/main/RxSwift/Observables/Debounce.swift

 

GitHub - ReactiveX/RxSwift: Reactive Programming in Swift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

https://github.com/ReactiveX/RxSwift/blob/main/RxSwift/Observables/Throttle.swift

 

GitHub - ReactiveX/RxSwift: Reactive Programming in Swift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

https://medium.com/fantageek/throttle-vs-debounce-in-rxswift-86f8b303d5d4

 

Throttle vs Debounce in RxSwift

When I went to reactivex.io, I got a whole lot of confusion between the terms throttle and debounce in the reactive programming world. I…

medium.com