본문 바로가기

iOS

[iOS] throttle, debounce

오늘은 Throttle, Debounce에 대해서 알아보겠습니다!

Debounce

지정한 시간 간격동안 들어오는 이벤트를 무시하고, 다시 시간 딜레이를 주는 개념 

Throttle

지정한 시간 간격동안 여러번 발생하는 이벤트 중 최초 또는 마지막의 이벤트를 딱 한번만 실행되도록 하는 개념

 

 

Debounce, Throttle의 개념만 살짝 알아봤는데요, 어떤 상황에서 사용하면 좋을까요?!
저는 서버와 통신 상황에서 Debounce를 사용했습니다.

검색API를 사용하는 뷰였는데요,

 

 

만약 위와 같이 서치바를 사용하는 뷰에서,

 

1. Textfield의 내용이 바뀔때마다 (.allEditingEvent) 서버에 POST 액션을 처리하고, 데이터를 받아오는 경우

>> 리소스의 낭비가 심하여 좋아보이지 않습니다.

 

2. Editing이 끝났을 때 ( .EditingDidEnd) 만 POST 액션을 처리하는 경우

>> 사용자 경험에 좋지 않을 것 같습니다! (자동완성이 없으므로)

 

이를 해결하기 위해서 Throttle과 Debounce를 이용해 보았습니다.

 

Debounce

맨 처음 설명에서 Debounce는 지정한 시간 간격동안 들어오는 이벤트를 무시하고, 다시 시간 딜레이를 주는 개념으로 설명을 했는데요,

그림에서 이해할 수 있듯, 매 실행마다 마지막 이벤트만 실행되는 것을 알 수 있습니다

그럼 어떤것이 이득이냐면?

 

  • 매번 실행되지 않음
  • 지정한 시간동안 이벤트가 추가로 들어오지 않는 경우 마지막 이벤트가 실행

코드를 보겠습니다!

class CustomTextField: UITextField {

    deinit {
        self.removeTarget(self, action: #selector(self.editingChanged(_:)), for: .editingChanged)
    }

    private var workItem: DispatchWorkItem?
    private var delay: Double = 0
    private var callback: ((String?) -> Void)? = nil

    func debounce(delay: Double, callback: @escaping ((String?) -> Void)) {
        self.delay = delay
        self.callback = callback
        DispatchQueue.main.async {
            self.callback?(self.text)
        }
        self.addTarget(self, action: #selector(self.editingChanged(_:)), for: .editingChanged)
    }

    @objc private func editingChanged(_ sender: UITextField) {
      self.workItem?.cancel()
      let workItem = DispatchWorkItem(block: { [weak self] in
          self?.callback?(sender.text)
      })
      self.workItem = workItem
      DispatchQueue.main.asyncAfter(deadline: .now() + self.delay, execute: workItem)
    }
}

 

아래와 같이 실행합니다.

   func debounceTextField() {
        searchTextField.debounce(delay: 0.1) { text in
            print(text)
        }
    }

 

따라서,

1. 0.1초 이전에 들어오는 이벤트는 무시하고, 

2. 0.1초동안 이벤트의 입력이 없다면 마지막 이벤트를 실행하게 됩니다.

 

 

Throttle

맨 처음에서, Throttle은 지정한 시간 간격동안 여러번 발생하는 이벤트 중 최초 또는 마지막의 이벤트를 딱 한번만 실행되도록 하는 개념

으로 설명했습니다! Rxmarble의 그림에서는 Throttle.first를 그려주어서 최초 이벤트가 발생하는것으로 그려졌는데,

 

최초 또는 마지막의 이벤트를 고를 수 있습니다

(RxSwift에서는 Throttle.first = false 이면 마지막 이벤트, Throttle.first = true이면 첫번째 이벤트)

라고 하는데, Swift에서는 코드를 변형해주면 되겠습니다.

 

class CustomTextField: UITextField {

    deinit {
        self.removeTarget(self, action: #selector(self.editingChanged(_:)), for: .editingChanged)
    }

    private var workItem: DispatchWorkItem?
    private var delay: Double = 0
    private var callback: ((String?) -> Void)? = nil

    func throttle(delay: Double, callback: @escaping ((String?) -> Void)) {
        self.delay = delay
        self.callback = callback
        DispatchQueue.main.async {
            self.callback?(self.text)
        }
        self.addTarget(self, action: #selector(self.editingChanged(_:)), for: .editingChanged)
    }

    @objc private func editingChanged(_ sender: UITextField) {
        if self.workItem == nil {
            let text = sender.text
            self.callback?(text)
            let workItem = DispatchWorkItem(block: { [weak self] in
                self?.workItem?.cancel()
                self?.workItem = nil
                if text != sender.text {
                    self?.callback?(sender.text)
                }
            })
            self.workItem = workItem
            DispatchQueue.main.asyncAfter(deadline: .now() + self.delay, execute: workItem)
        }
    }
}

Throttle의 경우에는, 지정된 시간 동안 가장 맨 처음 또는 맨 마지막 이벤트를 처리할 수 있습니다. (소스코드 >> 마지막 이벤트 처리)

서치바를 사용하는 경우에는 마지막 이벤트를 처리하는것이 좋으므로 마지막 이벤트를 처리합니다.

 

정리하면, 서치바의 경우와 같이 자동완성을 해야 하므로 

  1. 사용자 측면 - 검색되는 경험에 의해 Throttle이 유리

  2. 개발자 측면 - Debounce - 1번만 호출

으로 정리할 수 있습니다. 

 

 

https://docfriends.github.io/DevStrory/2019-01-29/swift-delay/ (debounce, Throttle)