GCD(Grand Central Dispatch)의 주요 개념과 사용 방법을 설명해주세요.
GCD(Grand Central Dispatch)는 Apple의 멀티스레딩 및 동시성 처리를 위한 프레임워크로, 효율적인 작업 분배와 스레드 관리를 가능하게 합니다. GCD를 사용하면 비동기 작업을 손쉽게 관리할 수 있으며, 애플리케이션의 성능을 향상시키는 데 유용합니다.
주요 개념
- 디스패치 큐 (Dispatch Queue)
- 작업을 실행할 큐를 관리하며, 작업의 실행 순서를 정의합니다.
- 종류:
- Serial Queue: 작업을 순차적으로 실행합니다. 하나의 작업이 끝나야 다음 작업을 시작합니다.
- Concurrent Queue: 작업을 동시에 실행합니다. 실행 순서는 보장되지 않지만, 각 작업은 병렬적으로 실행됩니다.
- Main Queue: 메인 스레드에서 실행되는 Serial Queue입니다. UI 업데이트는 반드시 Main Queue에서 실행해야 합니다.
- 동기/비동기 실행
- 동기 실행 (sync): 호출한 스레드에서 작업을 즉시 실행하고 완료될 때까지 기다립니다.
- 비동기 실행 (async): 호출한 스레드에서 작업을 큐에 추가하고 즉시 반환합니다. 작업 완료 여부와 관계없이 호출자는 계속 실행됩니다.
- QoS (Quality of Service)
- 작업의 중요도를 정의하며, 시스템이 우선 순위를 조정합니다.
- User-interactive: UI와 관련된 작업 (가장 높은 우선순위)
- User-initiated: 사용자가 요청한 작업
- Utility: 백그라운드 처리 작업
- Background: 사용자에게 보이지 않는 작업 (가장 낮은 우선순위)
- 작업의 중요도를 정의하며, 시스템이 우선 순위를 조정합니다.
- Work Item
- 큐에 추가할 작업 단위로, 클로저(closure)를 사용하여 정의합니다.
GCD 사용 방법: 종류별 자세한 설명과 예제
1. Global Queue
Global Queue는 시스템에서 제공하는 Concurrent Queue입니다. DispatchQueue.global(qos:)를 통해 접근할 수 있으며, 동시성 작업을 간단하게 실행할 때 유용합니다.
QoS(Quality of Service)를 설정하여 작업의 우선순위를 조정할 수 있습니다.
DispatchQueue.global(qos: .userInitiated).async {
// 이미지 다운로드 (비동기)
if let url = URL(string: "https://example.com/image.jpg"),
let data = try? Data(contentsOf: url),
let image = UIImage(data: data) {
DispatchQueue.main.async {
// UI 업데이트는 Main Queue에서 처리
self.imageView.image = image
}
}
}
- DispatchQueue.global(qos: .userInitiated)는 사용자 요청에 따라 이미지를 다운로드합니다.
- 다운로드가 완료되면 Main Queue에서 UI를 업데이트합니다.
2. Serial Queue
Serial Queue는 작업을 하나씩 순차적으로 실행합니다. 동시에 실행되지 않으므로 작업의 순서를 보장할 수 있습니다.
사용자가 직접 Serial Queue를 생성하여 특정 작업의 실행 순서를 제어할 수 있습니다.
let fileQueue = DispatchQueue(label: "com.example.fileQueue") // Serial Queue 생성
fileQueue.async {
print("Step 1: 파일 열기")
sleep(1)
print("Step 2: 데이터 쓰기")
sleep(1)
print("Step 3: 파일 닫기")
}
- Serial Queue는 작업을 순서대로 실행하므로 파일이 정상적으로 열리고, 데이터를 쓰고, 닫는 순서가 유지됩니다.
- 작업 간 충돌 없이 안전하게 실행됩니다.
3. Main Queue
Main Queue는 UI 업데이트 및 사용자와의 상호작용에 사용됩니다.
Main Queue는 Serial Queue이며, 메인 스레드에서 실행되기 때문에 반드시 UI 관련 작업은 Main Queue에서 처리해야 합니다.
@IBAction func loadDataButtonTapped(_ sender: UIButton) {
DispatchQueue.global(qos: .background).async {
// 백그라운드에서 데이터 로드
let data = self.fetchDataFromServer()
DispatchQueue.main.async {
// UI 업데이트
self.tableView.reloadData()
}
}
}
- DispatchQueue.global에서 데이터를 가져오는 작업을 실행합니다.
- 작업이 완료되면 Main Queue에서 UI를 업데이트하여 사용자 경험을 유지합니다.
4. 동기(sync)와 비동기(async)
동기 실행(sync)과 비동기 실행(async)은 작업을 추가하는 방식의 차이를 나타냅니다.
- sync: 호출한 스레드에서 작업이 완료될 때까지 기다립니다.
- async: 호출한 스레드에서 작업을 추가한 후 즉시 반환합니다.
let queue = DispatchQueue(label: "com.example.testQueue")
// 비동기 실행 (async)
queue.async {
print("Async 작업 1 시작")
sleep(2) // 작업 지연
print("Async 작업 1 완료")
}
print("Async 호출 후 다음 코드 실행 가능")
// 동기 실행 (sync)
queue.sync {
print("Sync 작업 2 시작")
sleep(2)
print("Sync 작업 2 완료")
}
print("Sync 호출 후 다음 코드 실행")
Async 호출 후 다음 코드 실행 가능
Async 작업 1 시작
Async 작업 1 완료
Sync 작업 2 시작
Sync 작업 2 완료
Sync 호출 후 다음 코드 실행
- 비동기 작업은 작업 큐에 추가한 뒤 바로 반환되므로 호출 이후의 코드를 즉시 실행할 수 있습니다.
- 동기 작업은 완료될 때까지 기다리므로 호출 이후의 코드 실행이 지연됩니다.
5. Dispatch Group
Dispatch Group은 여러 비동기 작업을 그룹으로 묶어 완료 시점을 추적하는 데 사용됩니다.
모든 작업이 끝난 후 후속 작업을 실행하고 싶을 때 유용합니다.
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
print("작업 1 시작")
sleep(2)
print("작업 1 완료")
group.leave()
}
group.enter()
DispatchQueue.global().async {
print("작업 2 시작")
sleep(3)
print("작업 2 완료")
group.leave()
}
group.notify(queue: .main) {
print("모든 작업 완료. UI 업데이트 실행")
}
작업 1 시작
작업 2 시작
작업 1 완료
작업 2 완료
모든 작업 완료. UI 업데이트 실행
- group.enter()와 group.leave()로 작업의 시작과 완료를 명시합니다.
- group.notify를 통해 모든 작업이 끝난 후 실행할 작업을 정의합니다.
직렬(Serial) 큐와 동시(Concurrent) 큐의 차이는 무엇인가요?
직렬 큐와 동시 큐는 작업 실행 방식의 차이를 나타냅니다. 두 큐는 모두 GCD에서 작업을 관리하는 데 사용되지만, 작업 처리 순서와 동시에 실행 여부에서 차이가 있습니다.
1. 직렬 큐 (Serial Queue)
특징
- 작업을 하나씩 순차적으로 실행합니다.
- 하나의 작업이 끝나야 다음 작업을 실행합니다.
- 실행 순서를 보장합니다.
- 작업이 동시에 실행되지 않으므로 스레드 충돌 위험이 적습니다.
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
serialQueue.async {
print("작업 1 시작")
sleep(1)
print("작업 1 완료")
}
serialQueue.async {
print("작업 2 시작")
sleep(1)
print("작업 2 완료")
}
serialQueue.async {
print("작업 3 시작")
sleep(1)
print("작업 3 완료")
}
작업 1 시작
작업 1 완료
작업 2 시작
작업 2 완료
작업 3 시작
작업 3 완료
- 작업들이 순서대로 하나씩 실행됩니다.
- 직렬 큐는 작업이 겹치지 않으므로 실행 순서를 보장합니다.
2. 동시 큐 (Concurrent Queue)
특징
- 작업을 동시에 실행합니다.
- 실행 순서는 작업이 큐에 추가된 순서와 같지만, 작업 완료 순서는 보장되지 않습니다.
- 작업이 병렬로 실행되므로 성능이 향상될 수 있습니다.
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
concurrentQueue.async {
print("작업 1 시작")
sleep(1)
print("작업 1 완료")
}
concurrentQueue.async {
print("작업 2 시작")
sleep(2)
print("작업 2 완료")
}
concurrentQueue.async {
print("작업 3 시작")
sleep(1)
print("작업 3 완료")
}
작업 1 시작
작업 2 시작
작업 3 시작
작업 1 완료
작업 3 완료
작업 2 완료
- 동시 큐에서는 작업이 병렬로 실행되기 때문에 작업 완료 순서는 달라질 수 있습니다.
3. Main Queue와 Global Queue의 연관성
- Main Queue:
- GCD의 직렬 큐 중 하나로, 메인 스레드에서 실행됩니다.
- UI 작업과 같이 순차적 처리가 필요한 작업에 사용됩니다.
- Global Queue:
- GCD의 동시 큐 중 하나로, 시스템에서 제공하는 병렬 처리 큐입니다.
- QoS(Quality of Service)를 통해 우선순위를 지정할 수 있습니다.
글로벌 큐(Global Queue)와 메인 큐(Main Queue)는 어떻게 다르나요?
Global Queue와 Main Queue는 모두 GCD에서 제공하는 디스패치 큐이지만, 목적과 실행 방식에서 차이가 있습니다.
1. 글로벌 큐 (Global Queue)
특징
- **시스템이 제공하는 동시 큐 (Concurrent Queue)**입니다.
- 여러 작업을 병렬로 실행할 수 있습니다.
- 작업의 실행 순서는 보장되지 않으며, 완료 순서도 다를 수 있습니다.
- **QoS (Quality of Service)**를 지정하여 작업의 중요도에 따라 우선순위를 조정할 수 있습니다.
사용 목적
- 백그라운드 작업, 병렬 처리가 필요한 연산 등에 사용됩니다.
- UI 업데이트와 같은 작업에는 적합하지 않습니다.
DispatchQueue.global(qos: .userInitiated).async {
print("글로벌 큐에서 실행되는 작업")
}
QoS 우선순위
QoS | 설명 |
.userInteractive | UI와 관련된 즉각적인 작업 (가장 높은 우선순위) |
.userInitiated | 사용자가 요청한 작업 (주로 데이터 로드 등) |
.utility | 백그라운드 작업 (오래 걸리는 작업, 낮은 우선순위) |
.background | 사용자에게 보이지 않는 작업 (예: 데이터 동기화, 가장 낮은 우선순위) |
2. 메인 큐 (Main Queue)
특징
- **직렬 큐 (Serial Queue)**이며, 메인 스레드에서 실행됩니다.
- UI 업데이트, 사용자와의 상호작용 등은 반드시 메인 큐에서 실행해야 합니다.
- 작업은 순차적으로 실행되며, 실행 순서를 보장합니다.
사용 목적
- UI 업데이트 작업, 사용자 입력 처리 등에 사용됩니다.
DispatchQueue.main.async {
print("메인 큐에서 실행되는 작업")
self.label.text = "UI 업데이트"
}
3. 두 큐의 조합
백그라운드에서 데이터를 처리한 뒤, 메인 큐에서 결과를 사용자에게 표시.
DispatchQueue.global(qos: .background).async {
let data = self.fetchData()
DispatchQueue.main.async {
self.updateUI(with: data)
}
}