[iOS] Swift에서 Debounce vs Throttle 차이점 (UIKit 예제)



# Swift에서 Debounce vs Throttle 차이점 (UIKit 예제)

안녕하세요 __물먹고하자__ 입니다 :)
지난번에 검색영역의 API를 붙이던중 __잦은 네트워크 중복방지__를 위해
__Debounce__를 적용했던점이 있는데, 내용을 찾아보니
__Debounce vs Throttle__ 해당내용이 많이 있더군요.

샘플링을 하면서 내용한번 정리해보려고 합니다.


---
## #.샘플링
> 💡 업무를 진행하던중 검색부분이 로컬 > API로 변경이되면서 __샘플링되었던 화면__
타이핑은 쭉 진행이되고, 0.5초기준점 들어왔던 __마지막 검색(Text)만 API 요청__

<!-- 1. 타이핑 이미지 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZTCpnXfd6eEJn9Im7r7v8m8Ak1hzzT0ksDkna9ApmDR4Cz2Kjrk5CqLGBOdsCTrqshyphenhyphenQ52Y5SFL3GilSM3q3Yks3TZbgfGm6BnzQVhGvr6e1LrVQvpbIp6pz645J6Bqv1F33SsdhxFG3eHxQodC8GYk6QlZZn5cwZD5WP388cMlYWu_F79jY7M0n8p2Yz/s1958/%E1%84%8C%E1%85%A1%E1%84%83%E1%85%A9%E1%86%BC%E1%84%8B%E1%85%AA%E1%86%AB%E1%84%89%E1%85%A5%E1%86%BC%E1%84%90%E1%85%A6%E1%84%89%E1%85%B3%E1%84%90%E1%85%B3.gif" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1958" data-original-width="922" height="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZTCpnXfd6eEJn9Im7r7v8m8Ak1hzzT0ksDkna9ApmDR4Cz2Kjrk5CqLGBOdsCTrqshyphenhyphenQ52Y5SFL3GilSM3q3Yks3TZbgfGm6BnzQVhGvr6e1LrVQvpbIp6pz645J6Bqv1F33SsdhxFG3eHxQodC8GYk6QlZZn5cwZD5WP388cMlYWu_F79jY7M0n8p2Yz/s600/%E1%84%8C%E1%85%A1%E1%84%83%E1%85%A9%E1%86%BC%E1%84%8B%E1%85%AA%E1%86%AB%E1%84%89%E1%85%A5%E1%86%BC%E1%84%90%E1%85%A6%E1%84%89%E1%85%B3%E1%84%90%E1%85%B3.gif"/></a></div>

---
## 1. Debounce
> *__마지막 입력만 처리__ (이것 때문에 사용하죠)
*사용자가 입력을 멈춘 뒤 설정한 시간(ms) 이후에 __최신 이벤트를 전달__
*주로 __검색 API 호출__ 같은 곳에서 사용
💡 예시상황
*사용자가 __h, he, hel, hell, hello 입력__
*0.5초 동안 아무 입력이 없을 때 &#8594; __"hello" 로 API 호출 1번 발생__

<!-- Debounce 이미지 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp09_H-PSZ8swvEMNK4iHLj_Z0Mipsp8qQJfErnAxE4EyMfqw3GJl5TI68sLWrv5ZJTLX22kLZA-30udVO3jJI8zoPFYr4dMj2ET0hRsSAu6X00HObhP7h0HTbYT_hOWnI2CXzQEzy3EGJsE4AbMpDeWWiYTUctm1_Wv2Pi-hw1kMpnzvET7pN_j9xnO79/s1958/Debounce%E1%84%8B%E1%85%A8%E1%84%89%E1%85%B5.gif" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1958" data-original-width="922" height="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp09_H-PSZ8swvEMNK4iHLj_Z0Mipsp8qQJfErnAxE4EyMfqw3GJl5TI68sLWrv5ZJTLX22kLZA-30udVO3jJI8zoPFYr4dMj2ET0hRsSAu6X00HObhP7h0HTbYT_hOWnI2CXzQEzy3EGJsE4AbMpDeWWiYTUctm1_Wv2Pi-hw1kMpnzvET7pN_j9xnO79/s600/Debounce%E1%84%8B%E1%85%A8%E1%84%89%E1%85%B5.gif"/></a></div>

---
## 2. Throttle
> *주기마다 가장 첫 입력 or 마지막 입력만 처리
*사용자가 입력을 빠르게 하더라도 설정한 주기마다 1번만 이벤트 전달
*주로 버튼 중복 클릭 방지, 위치 업데이트 등에 사용
💡 예시상황
*사용자가 h, he, hel, hell, hello 입력 (0.1초 간격)
*0.5초 throttle 적용 시 &#8594; "h", "he", "hel"&#8230; 중에서 일정 주기마다 1번씩 API 호출 발생

<!--Throttle 이미지 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYA61a0RMIkhVoWpf8LJ6iC1dZiRUC0T8C7RG1rF-CNh5ohJd6KxmIkStAV_1sY3ecWz63Zi02ZLlXZFWYugyBIVM2FqeDLV_y8W1mwuARMeNC8LbmlBy7-6gKTBWMgK-ZloEccMchyphenhyphen4kHAbz9hyphenhyphenD_rj7-3Q5rAZWt8YqNplIdGTp84q41mKlc7xkq3aTd/s1958/Thorttle%E1%84%8B%E1%85%A8%E1%84%89%E1%85%B5.gif" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="1958" data-original-width="922" height="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYA61a0RMIkhVoWpf8LJ6iC1dZiRUC0T8C7RG1rF-CNh5ohJd6KxmIkStAV_1sY3ecWz63Zi02ZLlXZFWYugyBIVM2FqeDLV_y8W1mwuARMeNC8LbmlBy7-6gKTBWMgK-ZloEccMchyphenhyphen4kHAbz9hyphenhyphenD_rj7-3Q5rAZWt8YqNplIdGTp84q41mKlc7xkq3aTd/s600/Thorttle%E1%84%8B%E1%85%A8%E1%84%89%E1%85%B5.gif"/></a></div>

---
## 3.샘플코드 부분
> 💡 __Debounce, Throttle__ 성향은 다르지만, 저의경우는 거의 __Debounce__ 하나만으로도 충분했던것 같습니다.
Debounce : 실시간검색시 일정 딜레이(이때가 제일 많이 활용)
Throttle : 버튼 액션에서 연달아 누르는 행위를 막을때
아래 샘플 코드로 직접 실행해 보시면 도움될 것 같습니다.


```swift
import UIKit
import Combine

class ViewController: UIViewController {
    
    private let textField = UITextField() // 검색어 영역
    private let resultLable = UILabel() // 결과부분
    
    private var cancellables = Set<AnyCancellable>()
    private let searchTrigger = PassthroughSubject<String, Never>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        // setupDebounce() // debounce 테스트
        setupThrottle() // Throttl 테스트
    }
    
    private func setupUI() {
        textField.borderStyle = .roundedRect
        textField.placeholder = "검색어 입력"
        textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
        
        view.backgroundColor = .systemBackground
        view.addSubview(textField)
        textField.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            textField.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50),
            textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            textField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
        
        resultLable.text = "검색이 될 결과 정보"
        view.addSubview(resultLable)
        resultLable.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            resultLable.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 50),
            resultLable.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
            resultLable.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40)
        ])
        
    }
    
    @objc private func textChanged() {
        searchTrigger.send(textField.text ?? "")
    }
    
    // &#9989; Debounce: 입력 멈춘 뒤 0.5초 후 마지막 값으로 호출
    private func setupDebounce() {
        searchTrigger
            .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
            .sink { text in
                print("🔍 API 호출 (Debounce) : \(text)")
                self.resultLable.text = text
            }
            .store(in: &cancellables)
    }
    
    // &#9989; Throttle: 0.5초마다 1번만 호출
    private func setupThrottle() {
        searchTrigger
            .throttle(for: .milliseconds(500), scheduler: RunLoop.main, latest: true)
            .sink { text in
                print("🔍 API 호출 (Throttle) : \(text)")
                self.resultLable.text = text
            }
            .store(in: &cancellables)
    }
}


```

---
## 마무리
가끔 어렵게 쓰고 있었던 부분들이 있었는데, 그중에 하나가 Debounce 였던 부분인것 같습니다.
별도로 생각안하고, "Task에 들어오는 값들을 취소하고 마지막걸 실행해야지~"
했던게 아예 Combine에 있었내요.

그래서 공부차원으로 변경했고 내용도 정리했습니다.

오늘은 이만~

즐거운 코딩 되게요.

끝.


댓글