-
DispatchQueue와 DispatchWorkItem 비교하기Apple🍎/Swift 2025. 4. 17. 18:50
DispatchQueue란?
DispatchQueue는 작업들을 FIFO(First-In-First-Out) 순서로 관리하고 실행하는 객체입니다. 주로 비동기 작업을 관리하고 백그라운드 스레드에서 코드를 실행할 때 사용합니다.
// 글로벌 큐를 사용한 비동기 작업 DispatchQueue.global().async { // 백그라운드에서 실행할 코드 print("백그라운드 작업 실행 중...") } // 메인 큐를 사용한 UI 업데이트 DispatchQueue.main.async { // UI 업데이트 코드 print("메인 스레드에서 UI 업데이트 중...") }
DispatchWorkItem이란?
DispatchWorkItem은 실행 가능한 코드 블록을 캡슐화한 객체입니다. 작업을 객체화함으로써 취소, 지연 실행, 완료 통지 등 더 세밀한 제어가 가능합니다.
let workItem = DispatchWorkItem { print("DispatchWorkItem이 실행되었습니다.") } // 디스패치 큐에서 워크아이템 실행 DispatchQueue.global().async(execute: workItem) // 또는 직접 실행 workItem.perform()
기본 사용법 비교
1. 간단한 비동기 작업
DispatchQueue만 사용할 경우
func fetchData() { DispatchQueue.global().async { // 데이터 가져오기 let data = self.performNetworkRequest() DispatchQueue.main.async { // UI 업데이트 self.updateUI(with: data) } } }
DispatchWorkItem을 사용할 경우
func fetchData() { let workItem = DispatchWorkItem { // 데이터 가져오기 let data = self.performNetworkRequest() DispatchQueue.main.async { // UI 업데이트 self.updateUI(with: data) } } DispatchQueue.global().async(execute: workItem) }
이 간단한 예시에서는 큰 차이가 없어 보이지만, 좀 더 복잡한 시나리오에서 DispatchWorkItem의 장점이 드러납니다.
DispatchWorkItem 활용하기
1. 작업 취소
DispatchWorkItem의 가장 큰 장점 중 하나는 작업을 취소할 수 있다는 점입니다.
var dataFetchWorkItem: DispatchWorkItem? func fetchDataWithCancellation() { // 이전 작업이 있다면 취소 dataFetchWorkItem?.cancel() // 새 작업 생성 let newWorkItem = DispatchWorkItem { // 작업 중간에 취소 여부 확인 if !Thread.current.isCancelled { let data = self.performLongNetworkRequest() // 다시 한번 취소 여부 확인 if !Thread.current.isCancelled { DispatchQueue.main.async { self.updateUI(with: data) } } } } // 작업 참조 저장 dataFetchWorkItem = newWorkItem // 작업 실행 DispatchQueue.global().async(execute: newWorkItem) } func cancelFetch() { dataFetchWorkItem?.cancel() }
이러한 기능은 사용자가 화면을 빠르게 전환하거나, 검색어를 연속해서 입력하는 경우 등 이전 작업을 취소해야 할 때 매우 유용합니다.
2. 작업 완료 통지
작업이 완료되었을 때 알림을 받을 수 있습니다
func fetchDataWithNotification() { let workItem = DispatchWorkItem { let data = self.performNetworkRequest() } // 작업 완료 시 실행될 코드 workItem.notify(queue: .main) { print("데이터 가져오기 완료!") self.hideLoadingIndicator() // 작업 완료시 메인쓰레드에 해당 작업 요청 } // 백그라운드에 비동기 작업 실행하고 DispatchQueue.global().async(execute: workItem) self.showLoadingIndicator() // 로딩 인디케이터 보여주고 }
3. 지연 실행과 취소
특정 시간 후에 작업을 실행하고, 필요시 취소하는 기능을 구현할 수 있습니다.
var searchWorkItem: DispatchWorkItem? func searchWithDebounce(query: String) { // 이전 검색 작업 취소 searchWorkItem?.cancel() // 새 검색 작업 생성 let newWorkItem = DispatchWorkItem { self.performSearch(query: query) } // 작업 참조 저장 searchWorkItem = newWorkItem // 0.5초 후에 검색 실행 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: newWorkItem) }
이 패턴은 사용자가 타이핑을 멈추고 일정 시간이 지난 후에만 검색을 실행하는 "디바운싱(debouncing)" 구현에 매우 유용합니다.
실용적인 예제
예제 1: 이미지 로더 구현
DispatchQueue만 사용한 경우
class ImageLoader { func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) { DispatchQueue.global().async { guard let data = try? Data(contentsOf: url), let image = UIImage(data: data) else { DispatchQueue.main.async { completion(nil) } return } DispatchQueue.main.async { completion(image) } } } }
DispatchWorkItem을 사용한 개선된 버전
class ImageLoader { private var loadTasks: [URL: DispatchWorkItem] = [:] func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) { // 이전 작업 취소 cancelLoad(for: url) let loadTask = DispatchWorkItem { guard let data = try? Data(contentsOf: url), let image = UIImage(data: data) else { DispatchQueue.main.async { completion(nil) } return } DispatchQueue.main.async { [weak self] in self?.loadTasks[url] = nil completion(image) } } // 작업 저장 및 실행 loadTasks[url] = loadTask DispatchQueue.global().async(execute: loadTask) } func cancelLoad(for url: URL) { loadTasks[url]?.cancel() loadTasks[url] = nil } func cancelAllLoads() { loadTasks.values.forEach { $0.cancel() } loadTasks.removeAll() } }
개선된 버전은 다음과 같은 이점이 있습니다
- 중복 요청 관리: 동일한 URL에 대한 이전 요청을 취소
- 메모리 관리: 작업이 완료되면 참조를 제거
- 일괄 취소: 모든 로드 작업을 한 번에 취소할 수 있음
예제 2: 타임아웃 구현
DispatchWorkItem으로 타임아웃 구현
func performTaskWithTimeout(timeoutSeconds: Double, completion: @escaping (Result<Data, Error>) -> Void) { let workItem = DispatchWorkItem { // 네트워크 요청 등 시간이 오래 걸릴 수 있는 작업 do { let data = try self.performLongRunningTask() // 작업이 취소되지 않았는지 확인 if !workItem.isCancelled { DispatchQueue.main.async { completion(.success(data)) } } } catch { if !workItem.isCancelled { DispatchQueue.main.async { completion(.failure(error)) } } } } // 작업 실행 DispatchQueue.global().async(execute: workItem) // 타임아웃 설정 DispatchQueue.main.asyncAfter(deadline: .now() + timeoutSeconds) { if !workItem.isCancelled { workItem.cancel() let timeoutError = NSError(domain: "com.example", code: -1, userInfo: [NSLocalizedDescriptionKey: "작업 시간 초과"]) completion(.failure(timeoutError)) } } }
이 패턴은 네트워크 요청이나 오래 걸리는 작업에 타임아웃을 설정할 때 매우 유용합니다.
예제 3: 검색 자동완성 구현
class SearchController { private var searchWorkItem: DispatchWorkItem? func search(query: String) { // 이전 검색 취소 searchWorkItem?.cancel() // 검색어가 비어있으면 결과 초기화 if query.isEmpty { self.updateSearchResults([]) return } // 새 검색 작업 생성 let workItem = DispatchWorkItem { [weak self] in guard let self = self else { return } // 네트워크 요청으로 자동완성 결과 가져오기 let results = self.fetchAutocompleteSuggestions(for: query) // 메인 스레드에서 UI 업데이트 if !workItem.isCancelled { DispatchQueue.main.async { self.updateSearchResults(results) } } } // 작업 참조 저장 searchWorkItem = workItem // 0.3초 지연 후 검색 실행 (디바운싱) DispatchQueue.global().asyncAfter(deadline: .now() + 0.3, execute: workItem) } private func fetchAutocompleteSuggestions(for query: String) -> [String] { // 실제 구현에서는 네트워크 요청 또는 로컬 데이터베이스 검색 return ["결과 1", "결과 2", "결과 3"] } private func updateSearchResults(_ results: [String]) { // UI 업데이트 로직 print("검색 결과 업데이트: \(results)") } }
이 예제는 사용자가 검색창에 타이핑할 때마다 이전 검색을 취소하고 짧은 지연 후에 새 검색을 실행하는 디바운싱 패턴을 구현합니다. 이는 UX 개선과 서버 부하 감소에 도움이 됩니다.
언제 DispatchWorkItem을 사용해야 할까?
일반적으로 다음과 같은 경우에 DispatchWorkItem이 유용합니다:
- 작업 취소가 필요한 경우
- 사용자 인터랙션에 의해 진행 중인 작업을 취소해야 할 때
- 화면 전환이나 뷰 컨트롤러 해제 시 백그라운드 작업을 취소해야 할 때
- 작업 완료 알림이 필요한 경우
- 여러 작업이 완료된 후 특정 동작을 수행해야 할 때
- 작업 완료 시점을 정확히 알아야 할 때
- 지연 실행과 타임아웃이 필요한 경우
- 디바운싱이나 쓰로틀링을 구현할 때
- 작업에 시간 제한을 설정할 때
- 작업 상태 추적이 필요한 경우
- 작업의 취소 여부를 코드 내에서 확인해야 할 때
- QoS(서비스 품질) 설정이 필요한 경우
- 작업 우선순위를 세밀하게 제어해야 할 때
// QoS 설정 예시 let highPriorityTask = DispatchWorkItem(qos: .userInitiated) { // 사용자 응답성에 중요한 작업 } let lowPriorityTask = DispatchWorkItem(qos: .background) { // 백그라운드 처리 작업 }
'Apple🍎 > Swift' 카테고리의 다른 글
좀 더 Low 하게 가보자~ Swift 저수준 메모리 관리 (0) 2025.04.25 Swift 동시성 모델 (0) 2025.04.18 Swift에서의 데이터 레이스 방지를 위한 동기화 기법 (0) 2025.04.16 Swift 6 : Typed Throws ( 에러도 타입을 줘서 더 명확히 처리하자.) (0) 2025.04.14 inout 파라미터 작동 방식 파해치기 (0) 2025.03.11