반응형

Swift Throttle vs Debounce 완벽 가이드

 

1. 개념 요약

구분 Debounce Throttle
동작 개념 “입력이 멈출 때까지 기다렸다가 실행” “일정 주기마다 한 번만 실행”
핵심 목적 연속 입력 중복 방지 지속 입력 속도 제어
대표 상황 검색창 자동완성, 타이핑 이벤트 스크롤 이벤트, 센서 데이터, 위치 업데이트

 

2. 비유로 이해하기

상황 설명
Debounce 사용자가 타이핑을 멈출 때까지 기다렸다가 서버 검색 실행. (입력이 멈추면 한 번만)
Throttle 초당 한 번만 서버에 위치 업데이트. (입력이 계속돼도 일정 간격으로만 실행)

 

3. 타임라인 예시


🕒 Debounce

사용자 입력: a----b----c-----------[멈춤]---(발사)
실행 타이밍:                          ↑ (마지막 입력 후 N초 경과)

🕒 Throttle

이벤트 발생: |■■■■■■■■■■■■■■■■■■■■|
실행 타이밍: |■     ■     ■     ■     ■     |
(주기마다 한 번씩 실행)

 

4. Swift Combine 예제

✅ Debounce (검색창 자동완성 예시)

import Combine
import SwiftUI

class SearchViewModel: ObservableObject {
    @Published var query = ""
    @Published var results: [String] = []

    private var cancellables = Set<AnyCancellable>()

    init() {
        $query
            .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }

    func performSearch(_ text: String) {
        guard !text.isEmpty else { return }
        print("검색 실행: \(text)")
        // 실제 API 요청 로직
    }
}

✅ Throttle (실시간 위치 업데이트 예시)

import Combine
import CoreLocation

class LocationManager: NSObject, ObservableObject {
    @Published var coordinate: CLLocationCoordinate2D?
    private var cancellables = Set<AnyCancellable>()
    private let subject = PassthroughSubject<CLLocationCoordinate2D, Never>()

    override init() {
        super.init()
        subject
            .throttle(for: .seconds(2), scheduler: RunLoop.main, latest: true)
            .sink { [weak self] location in
                self?.coordinate = location
                print("위치 업데이트: \(location.latitude), \(location.longitude)")
            }
            .store(in: &cancellables)
    }

    func didUpdateLocation(_ loc: CLLocationCoordinate2D) {
        subject.send(loc)
    }
}

 

5. UIKit 예제

Debounce로 버튼 중복 클릭 방지

class ViewController: UIViewController {
    private var cancellables = Set<AnyCancellable>()
    private let buttonTap = PassthroughSubject<Void, Never>()

    override func viewDidLoad() {
        super.viewDidLoad()

        buttonTap
            .debounce(for: .milliseconds(800), scheduler: RunLoop.main)
            .sink { [weak self] _ in
                self?.doAction()
            }
            .store(in: &cancellables)
    }

    @IBAction func buttonPressed(_ sender: UIButton) {
        buttonTap.send()
    }

    func doAction() {
        print("버튼 실행 (디바운스)")
    }
}

6. SwiftUI + Throttle (스크롤 감속 제어)

import SwiftUI
import Combine

struct ScrollThrottleView: View {
    @State private var scrollPosition: CGFloat = 0
    private let scrollSubject = PassthroughSubject<CGFloat, Never>()
    @State private var cancellables = Set<AnyCancellable>()

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                ForEach(0..<200) { i in
                    Text("Item \(i)")
                        .font(.title3)
                }
            }
            .background(GeometryReader { geo in
                Color.clear
                    .onChange(of: geo.frame(in: .global).minY) { y in
                        scrollSubject.send(y)
                    }
            })
        }
        .onAppear {
            scrollSubject
                .throttle(for: .milliseconds(300), scheduler: RunLoop.main, latest: true)
                .sink { value in
                    scrollPosition = value
                    print("스크롤 감속 업데이트:", value)
                }
                .store(in: &cancellables)
        }
    }
}

7. Combine vs RxSwift 비교

구분 Combine RxSwift
Throttle .throttle(for:scheduler:latest:) .throttle(.milliseconds(n), latest: true, scheduler: MainScheduler.instance)
Debounce .debounce(for:scheduler:) .debounce(.milliseconds(n), scheduler: MainScheduler.instance)
공통점 이벤트 폭주 제어, 백프레셔(Backpressure) 대응 이벤트 조절 연산자(Operators)
차이점 Combine은 타입 안정성이 강함 RxSwift는 범용적 연산자 조합이 자유로움

 

8. 주의사항

상황 Debounce Throttle
사용자가 계속 입력 중일 때 실행 안 됨 주기마다 실행
마지막 입력 이후 동작 마지막 이벤트 실행 마지막 주기 이벤트만 실행
UI 연동 시 검색창텍스트 입력 스크롤제스처센서 데이터
Combine에서 스케줄러 누락 시 메인 스레드 미갱신 UI 갱신 오류 발생 가능

 

9. 성능 및 UX 측면 정리

측면 Debounce Throttle
API 요청 부하 감소 매우 큼 중간
UI 반응성 약간 늦음 일정함
정확도 마지막 상태 반영 일정 주기로만 반영
추천 사용 예시 검색, 자동완성, 텍스트 필터 위치, 스크롤, 센서 이벤트

 

10. 결론

  • Debounce → “입력이 끝났을 때 한 번만”
  • Throttle → “계속 입력돼도 주기마다 한 번만”

둘 다 이벤트 폭주(과도한 트리거) 를 방지하기 위한 반응형 프로그래밍의 필수 연산자입니다.

반응형
Posted by 까칠코더
,