- Swift 5 기준
iOS 앱을 개발하면서 비동기 처리와 관련해 GCD(Grand Central Dispatch) 를 많이 듣게 되죠. DispatchQueue
라고 하면 더 친숙할텐데, GCD에 대해 공부한 점을 정리해보려고 합니다. 😃
혹시 잘못된 정보는 피드백 부탁드립니다~!
Serial, Concurrent
DispatchQueue
에는 Serial queue
와 Concurrent queue
가 있어요. Serial queue
는 순차적으로 작업을 처리하는 직렬의 queue이고 Concurrent queue
는 순서를 보장하지 않고 동시에 작업하는 병렬의 queue라고 할 수 있어요.
Sync, Async
각 queue는 Sync
와 Async
로 작업을 또 나눌 수 있는데 Serial Sync, Serial Async
와 Concurrent Sync, Concurrent Async
로 작성할 수 있어요.
Sync
는 큐에 추가된 작업이 종료될 때까지 기다리는 것이고, Async
는 큐에 작업을 추가하지만 완료 여부는 보장하지 않아요.
Queue
애플 도큐먼트에서 정의하고 있는 Queue에 대해서 간단히 정리해볼게요.
- MainQueue: Main Thread에서 처리되는 Serial Queue
- GlobalQueue: 전체 시스템에 공유되는 Concurrent Queue. QoS 구분해 사용
- CustomQueue: GlobalQueue에서 수행되는 임의로 정의한 Queue. Serial & Concurrent 모두 가능하나 보통 Serial 작업 수행. Main과 구분해 사용이 필요할 때 사용
QoS(Quality of Service)
global queue
에서 Concurrent한 작업을 처리할 때 QoS
로 작업의 우선순위를 정할 수 있어요. QoS
는 중요도와 우선순위에 따라 아래 4가지로 구분할 수 있어요.
- UserInteractive: 사용자가 즉각적으로 반응을 확인해야하는 작업. 가볍고 신속히 처리가 필요한 작업용
- UserInitiated: UserInteractive 다음으로 사용자가 빠르게 반응을 확인해야하는 작업. 반응성과 성능이 중점적인 작업용
- Utility: 작업을 완료하는데 약간의 시간이 소요되는 작업. 데이터 다운로드같은 작업으로 즉각적인 결과가 필요하지 않은 작업용
- Background: 동기화 및 백업과 같이 사용자가 인지할 수 없는 작업. 에너지 효율 중점적 작업용
Task Test
그럼 아래와 같은 for 문을 다양한 queue에서 실행해보고, 작업 완료되는 순서에 대해 알아볼까요?
for forLoop(_ log: String) {
var result: Int = 0
for _ in 1...10000 {
result += 1
}
print("\(log) = \(String(result))"
}
+1이 되도록 1만번 반복되는 함수예요.
Main queue
// Main queue: global이 실행된 이후 순차적으로 실행된다.
DispatchQueue.main.async {
forLoop("mainAsync")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
forLoop("mainAfterAsync")
}
Global queue
// Global queue: 순서 상관없이 병렬 실행된다.
// 1과 2는 순서 상관없이 print 된다.
DispatchQueue.global().async {
forLoop("globalAsync1")
}
DispatchQueue.global().async {
forLoop("globalAsync2")
}// Custom queue: 순서 상관없이 병렬 실행된다. 같은 label 내에서의 순서만 보장된다.
// customQ1 끼리는 순차실행, customQ1과 customQ2의 순서는 보장되지 않는다.
let customQ1: DispatchQueue = DispatchQueue(label: "custom1")
let customQ2: DispatchQueue = DispatchQueue(label: "custom2")
customQ1.async {
forLoop("custom1Async1")
}
customQ1.async {
forLoop("custom1Async2")
}
customQ2.async {
forLoop("custom2Async1")
}
customQ2.async {
forLoop("custom2Async2")
}
Global queue with QoS
// 아래 순서대로 실행된다.
DispatchQueue.global(qos: .userInteractive).async {
forLoop("userInteractive")
}DispatchQueue.global(qos: .userInitiated).async {
forLoop("userInitiated")
}DispatchQueue.global(qos: .utility).async {
forLoop("utility")
}DispatchQueue.global(qos: .background).async {
forLoop("background")
}
위 코드를 Playground에서 붙여넣고 실행하면 QoS를 적용하지 않은 global queue가 제일 먼저 실행되고 (병렬 또는 직렬로) 그 다음 QoS를 적용한 코드, 가장 마지막으로 main queue의 코드가 실행되는 것을 알 수 있어요.
Conclusion
async 뿐만 아니라 sync도 사용 가능하며, sync는 작업이 끝날 때까지 기다리기 때문에 UI 업데이트는 main thread의 async만 사용 가능해요.
raywenderlich 예제에서는 UI 업데이트를 위해 아래와 같은 코드를 작성하고 있어요.
DispatchQueue.global(qos: .userInteractive).async {
forLoop("선행 작업") DispatchQueue.main.async {
print("선행 작업 후 UI 변경")
}
}
이렇게 하면 서버에서 데이터를 받아오거나, 사전에 처리해야할 부분이 있을 경우 UI 업데이트를 자연스럽게 할 수 있어요. 👏