[iOS/SwiftUI] Debounce 사용해보기
오늘은 디바운스에 대해 알아보자!
💡Debounce vs throttle
Debounce : 일정시간 내에 이벤트를 실행했을 때 마지막(또는 첫 번째) 이벤트만 실행되도록 하여 일정시간이 지난 후에 이벤트를 트리거 시킨다.
Throttle : 지정된 시간 간격으로 구독한 최근 값(마지막 이벤트)을 publish한다. 입력 후 바로 입력된 다음 대기 상태로 넘어간다.
디바운스에 대해 자세히보면 combine에 속한 메서드이다. 그래서 꼭 combine을 import 해줘야 하고, for dueTime과 scheduler는 debounce를 사용할 때 필수로 지정해줘야 하는 값이기 때문에 잘 알아보고 넘어가자!
- for dueTime: 실행시킬 이벤트 시간
- scheduler: publisher가 요소를 어디에 전달할껀지
나는 사용자가 특정 버튼을 여러번 눌렀을 때 그 이벤트를 다루기 위해서 디바운스를 사용했다!
구현코드
import Combine
import SwiftUI
class InquiryViewModel: ObservableObject {
...
var cancellables = Set<AnyCancellable>()
let debounceInterval = 0.3
var debounceTimer = PassthroughSubject<Void, Never>()
var dismissAction: (() -> Void)?
init() {
debounceTimer
.debounce(for: .seconds(debounceInterval), scheduler: RunLoop.main)
.sink { [weak self] in
self?.sendInquiryMailApi { success in
if success {
self?.dismissAction?()
Log.debug("디바운싱 문의하기 마지막 이벤트 보냄")
} else {
Log.debug("문의하기 디바운싱 실패")
}
}
}
.store(in: &cancellables)
}
앞에서 말했듯이 debounce를 사용하려면 combine을 꼭 import 해줘야 한다. cancellables를 통해 publisher에 대한 구독을 저장하고 있기 때문에!!!
나는 문의하기 api를 호출하는 버튼을 클릭했을때 디바운싱이 실행되게 구현하였고, viewModel내부에서 초기화를 시켜주었다.
.debounce(for: .seconds(debounceInterval), scheduler: RunLoop.main)
위 코드를 통해 0.3초동안의 이벤트 지연시간을 지정해 주었고, 이 지정시간이 끝나면 가장 최근의 이벤트 한 개만 발생시켜 준다.
그리고 메인스레드에서 publisher 요소를 전달하겠다는 의미로 RunLoop.main을 사용해 주었다.
(보통 UI관련은 메인스레드에서 작업한다고 한다!)
그래서 다른 블로그들을 참고해도 디바운싱이나 쓰트롤링을 처리하려면 텍스트필드나 버튼이랑 상호작용이 돼야 되기 때문에 다 메인으로 되어 있을 것이다!
sink
sink는 Combine에서 퍼블리셔의 값을 구독하는 방법 중 하나이다! (더 자세하게 들어가면 combine 포스팅이 될 거 같아서 생략)
sink { ... } 내부로직으로 인해 디바운싱된 publisher에 구독하도록 한다. 그래서 클로저 내부에서 문의하기 api를 호출시키고, 성공했다면
지정한 액션을 수행하도록 설계하였다.
store
store는 sink를 통해 받은 구독을 cancellables 저장하여 참조를 유지한다.
store(in: &cancellables)을 통해서 viewModel이 살아있는 동안에 구독이 유지되도록 한다.
이쯤 되면 한 가지 의문이 들 수도 있다! cancellables에 굳이 저장 안 해도 되지 않나?? 하는 생각이 들 수도 있다
안타깝게도 combine을 사용하는데 cancellables에 구독을 보관/저장하지 않으면 즉시 취소되어 디바운싱이 작동하지 않을 수 있다!!
이외에도 combine을 사용하려면 거의 모든 메서드들이 store를 통해 받은 구독을 저장해줘야 한다.
이해가 안 간다면 combine의 기본 동작원리 정도 공부하고 다시 이 블로그를 본다면 무슨 말인지 이해할 것이다
https://yanni13.tistory.com/16
[swift] Combine이랑 ObservableObject 예제로 파헤치기
combine과 observableobject를 사용하여 간단하게 구현해 보고 비교해보려 한다. 2024.03.15 - [Swift] - [swift] Combine & ObservableObject [swift] Combine & ObservableObject 오늘은 swiftUI에서 꼭 알아야 하는 Combine랑 Observabl
yanni13.tistory.com
CustomBottomButton(action: {
continueButtonAction()
}, label: "문의하기", isFormValid: $viewModel.isFormValid)
...
private func continueButtonAction() {
if viewModel.isFormValid {
viewModel.dismissAction = {
self.presentationMode.wrappedValue.dismiss()
}
// 디바운스 타이머 트리거
viewModel.debounceTimer.send(())
}
}
다시 코드로 돌아와서, 폼의 상태가 모두 유효하다면 디바운스 타이머 트리거를 실행시키는 viewModel.debounceTimer.send()
를 통해서 지정된 초로 디바운스 타이머를 트리거 시켜주고 , viewModel.dismissAction을 통해 문의하기 api요청에 성공했을 경우 현재 창을 닫도록 구현하였다!
viewModel.debounceTimer.send()
- PassthroughSubject를 사용하여 외부에서 이벤트를 방출하는 메서드
- send()는 사용자의 입력을 받아 디바운싱된 동작을 실행하기 위해 사용한다!
- 그래서 지정된 초에 몇 번을 누르던지 간에 debounce(for:scheduler:)에 의해서 마지막 이벤트 하나를 보내게 된다!
마무리
내가 디바운스를 사용한 이유는 api를 호출할 때 돌아오는 응답이 늦기 때문에 사용자가 버튼을 여러 번 클릭했을 경우 일정시간이 지난 후에 트리거 되도록 하는 디바운스를 사용했다.
하지만 다른 자료들을 더 찾아보니 보통 텍스트 필드에 디바운스를 사용하고 버튼 중복 이벤트에 대해서는 쓰로틀링을 사용하더라!!
어떻게 이벤트를 담아두고 있다가 어느 시점에서 트리거 해줄지에 대한 차이점만 다를 뿐 대기하고 있다가 일정 값을 전달하는 방법에는 일치하기 때문에 무엇을 사용하던지 정답은 없다고 생각한다