BIBI BLOG
🚀 [SwiftUI] Async/await로 Debounce 적용하기 본문
🚀 [SwiftUI] Async/await로 Debounce 적용하기 (AsyncAlgorithms 활용)
Swift의 Concurrency 기능이 도입되면서, 우리는 Combine을 덜 사용하고 async/await과 AsyncStream을 더 많이 활용하게 되었습니다.
하지만! Combine에서 제공하는 debounce, throttle 같은 기능이 async/await에서는 기본적으로 제공되지 않아요.
👉 하지만 AsyncAlgorithms 라이브러리를 사용하면 이 문제를 해결할 수 있습니다! 🎉
👉 오늘은 AsyncAlgorithms을 활용하여 SwiftUI에서 debounce를 적용하는 방법을 쉽게 설명해 드릴게요!
1️⃣ Debounce란?
Debounce(디바운스)란 사용자가 빠르게 입력할 때, 일정 시간 동안 입력이 멈추면 한 번만 실행되는 기능입니다.
예를 들어, 검색창에서 사용자가 타이핑할 때 매번 API 요청이 가는 것을 방지하고 싶다면 debounce를 사용하면 됩니다!
📌 예시:
- ❌ 디바운스가 없을 때: "S", "Sw", "Swi", "Swif", "Swift" → 총 5번 API 호출 (비효율적)
- ✅ 디바운스를 적용하면: "Swift" → 1번만 API 호출됨! (최적화)
2️⃣ 기본적인 SwiftUI 코드
아래와 같이 TextField(입력창)를 만들어 사용자 입력을 받는 기본 코드가 있습니다.
import SwiftUI
import AsyncAlgorithms
struct ContentView: View {
@State private var query = ""
var body: some View {
TextField("검색어를 입력하세요...", text: $query)
}
}
✅ 하지만 문제점!
현재 코드는 사용자가 글자를 입력할 때마다 API를 호출할 가능성이 있습니다.
👉 이를 해결하려면 입력을 일정 시간 기다렸다가(0.3초) API를 호출하는 debounce 기능을 추가해야 합니다!
3️⃣ AsyncAlgorithms을 활용한 Debounce 적용
Swift의 AsyncAlgorithms을 사용하면 Combine 없이도 debounce를 쉽게 구현할 수 있습니다! 🎯
📌 AsyncChannel을 사용하여 debounce 적용하기
import SwiftUI
import AsyncAlgorithms
struct ContentView: View {
@State private var query = ""
private let queryChannel = AsyncChannel<String>() // ✅ 입력값을 처리할 채널
var body: some View {
TextField("검색어를 입력하세요...", text: $query)
.task(id: query) { // ✅ 입력값이 변경될 때마다 실행
await queryChannel.send(query)
}
.task { // ✅ AsyncChannel을 debounce 처리 후 API 요청
for await query in queryChannel.debounce(for: .seconds(0.3)) {
await fetchSearchResults(for: query)
}
}
}
private func fetchSearchResults(for query: String) async {
print("검색 실행: \(query)") // ✅ 여기에 실제 API 호출 코드 추가 가능!
}
}
✅ 이제 사용자가 글자를 입력해도 일정 시간(0.3초) 동안 입력이 없을 때만 API를 호출합니다! 🚀
✅ AsyncChannel<String>()을 사용하여 query 값을 관리하고, debounce 기능을 적용했습니다.
4️⃣ Debounce 기능을 손쉽게 재사용하기 (View Extension 만들기)
여러 TextField에서 debounce를 쉽게 적용할 수 있도록 View 확장(extension)을 만들 수 있습니다.
📌 View Extension으로 재사용 가능하게 만들기
import SwiftUI
import AsyncAlgorithms
extension View {
func debounce<T: Sendable & Equatable>(
_ query: Binding<T>,
using channel: AsyncChannel<T>,
for duration: Duration,
action: @Sendable @escaping (T) async -> Void
) -> some View {
self
.task {
for await query in channel.debounce(for: duration) {
await action(query)
}
}
.task(id: query.wrappedValue) {
await channel.send(query.wrappedValue)
}
}
}
✅ 이제 View에서 debounce를 쉽게 적용할 수 있습니다!
✅ TextField에서 debounce를 1줄로 적용 가능!
5️⃣ Debounce를 쉽게 적용한 코드
위에서 만든 debounce 확장을 사용하면 1줄로 debounce 적용 가능!
import SwiftUI
import AsyncAlgorithms
struct ContentView: View {
@State private var query = ""
private let queryChannel = AsyncChannel<String>() // ✅ AsyncChannel 생성
var body: some View {
TextField("검색어를 입력하세요...", text: $query)
.debounce($query, using: queryChannel, for: .seconds(0.3), action: fetchSearchResults)
}
private func fetchSearchResults(for query: String) async {
print("검색 실행: \(query)")
}
}
✅ debounce($query, using: queryChannel, for: .seconds(0.3), action: fetchSearchResults)
✅ 이제 debounce를 쉽게 사용할 수 있습니다! 🎉
6️⃣ MVVM 패턴에서 Observable을 사용하여 적용하기
👉 MVVM 패턴에서는 ViewModel을 사용하여 로직을 분리하면 더 깔끔합니다!
📌 ViewModel 만들기
import Observation
import AsyncAlgorithms
@Observable
final class ViewModel {
var query = ""
@ObservationIgnored let queryChannel = AsyncChannel<String>()
func fetchSearchResults(for query: String) async {
print("검색 실행: \(query)")
}
}
📌 View에서 적용하기
import SwiftUI
struct ContentView: View {
@State private var viewModel = ViewModel() // ✅ ViewModel 사용
var body: some View {
TextField("검색어를 입력하세요...", text: $viewModel.query)
.debounce($viewModel.query, using: viewModel.queryChannel, for: .seconds(0.3), action: viewModel.fetchSearchResults)
}
}
✅ MVVM 구조에서도 쉽게 debounce 적용 가능!
✅ ViewModel에서 query 상태를 관리하여 코드가 더 깔끔해짐!
🎯 마무리
✅ Swift의 AsyncAlgorithms을 활용하면 Combine 없이도 debounce를 구현할 수 있습니다!
✅ AsyncChannel을 사용하여 비동기 데이터 흐름을 쉽게 관리할 수 있습니다.
✅ View 확장을 사용하면 1줄로 debounce 적용이 가능!
✅ MVVM 패턴에서도 쉽게 활용할 수 있습니다.
자료
debounce-with-async-await-asyncalgorithms-in-swiftui-and-observable-macro
'iOS > Swift' 카테고리의 다른 글
📱 SwiftUI에서 메모리 사용 줄이기! ⚡ (0) | 2025.03.14 |
---|---|
Swift에서 자주 사용하는 모델 프로토콜 🎯 (0) | 2025.03.11 |
Swift Actors: 멀티스레드 버그 잘가~👋 (0) | 2025.03.05 |
Swift Style Guide: 애플 스타일로 작성하는 방법:Part 2📝 (0) | 2025.02.28 |
Swift Style Guide: 애플 스타일로 작성하는 방법:Part 1📝 (1) | 2025.02.26 |