이 글은 공식문서를 번역해 나만의 언어로 작성한 글입니다.
의역이나 오역이 있을 수 있으니 조심하세요! 😨
개요
GCD(Grand Central Dispatch)라고도 알려진 Dispatch는 macOS, iOS, watchOS 및 tvOS의 멀티코어 하드웨어에서 동시 코드 실행을 지원하는 체계적이고 포괄적인 개선을 제공하는 언어 기능, 런타임 라이브러리 및 시스템 향상 기능을 포함합니다.
BSD 하위 시스템, Core Foundation 및 Cocoa API는 모두 이러한 향상된 기능을 사용하여 시스템과 애플리케이션을 보다 빠르고 효율적으로 실행하고 응답성을 향상시키도록 확장되었습니다.
컴퓨팅 코어 수가 다른 여러 컴퓨터나 여러 애플리케이션이 경쟁하는 환경에서 단일 애플리케이션이 여러 개의 코어를 효과적으로 사용하는 것이 얼마나 어려운지를 생각해 보십시오.
시스템 수준에서 작동하는 GCD는 사용 가능한 시스템 리소스와 균형 있게 일치시켜 실행 중인 모든 애플리케이션의 요구 사항을 더 잘 수용할 수 있습니다.
GCD 와 Dispatch 가 같은 말이었네요. 이렇게 공식문서를 통해 또 하나 배워갑니다!
DispatchQueue
앱의 기본 스레드 또는 배경 스레드에서 작업 실행을 직렬 또는 동시에 관리하는 개체입니다.
DispatchQeueue 에선 위 속성을 사용해 MainQueue 와 GlobalQueue를 정하게 되고, 어떤 큐에 작업을 넣는지에 따라 앱의 기본 스레드(메인 스레드) 또는 백그라운드 스레드에서 동작하게 됩니다.
디스패치 큐는 응용 프로그램이 블록 객체의 형태로 작업을 제출할 수 있는 FIFO 큐입니다.
디스패치 대기열은 작업을 연속적으로 또는 동시에 실행합니다.
대기열을 디스패치하기 위해 제출된 작업은 시스템에서 관리하는 스레드 풀에서 실행됩니다.
앱의 기본 스레드를 나타내는 디스패치 대기열을 제외하고 시스템은 작업을 실행하는 데 사용하는 스레드에 대해 보장하지 않습니다.
디스패치 큐는 큐라는 이름에 맞게 FIFO(First In First Out)으로 동작하는 큐네요.
우리가 작업을 실행하기 위해 디스패치 큐에 작업을 넣으면, MainQueue를 제외한 나머지 큐는 어떤 스레드가 사용될지 보장해주지 않는다고 합니다.
작업 항목을 동기식 또는 비동기식으로 예약할 수 있습니다.
작업 항목을 동기적으로 예약하면 코드는 해당 항목이 실행을 완료할 때까지 기다립니다.
작업 항목을 비동기식으로 예약하면 작업 항목이 다른 곳에서 실행되는 동안 코드가 계속 실행됩니다.
어느 큐를 사용하는지에 따라 작업을 수행할 스레드가 결정되고, 또한 그 작업이 동기식과 비동기식 중 어떻게 수행될 것인지를 정해줄 수 있습니다.
중요
기본 대기열에서 작업 항목을 동기적으로 실행하려고 하면 교착 상태가 발생합니다.
Attempting to synchronously execute a work item on the main queue results in deadlock.
MainQueue를 통해 메인스레드에서 작업을 실행하도록 할 때, 동기 방식을 사용하려고 하면 데드락에 빠지게되어 주의해야 합니다.
왜 데드락에 빠지는지 궁금하다면 이곳을 참고하면 좋을 것 같습니다.
DispatchQueue의 종류
DispatchQueue에는 3가지 종류의 Queue가 있습니다.
- MainQueue
- GlobalQueue
- Custom(Private)Queue
MainQueue 의 특성
- 오직 한개만 존재합니다.
- Serial 특성을 가진 Queue입니다.
- 이 곳에 할당된 작업들은 메인 스레드에서 처리됩니다.
GlobalQueue 의 특성
- Concurrent한 특성을 가진 Queue입니다.
- QoS(Quality Of Service)에 따라 6종류로 나뉩니다.
- QoS의 우선순위에 따라 더 많은 스레드를 배치합니다.
- Concurrent한 특성 때문에 이 곳에 할당된 작업들을 여러 스레드에 분산시켜 할당합니다.
GlobalQueue의 QoS 종류
UserInteractive
사용자와 직접 상호작용 하는 작업. (ex. UI 업데이트, 애니메이션 등)
사용자의 행동에 대한 즉각적인 반응이 기대 되지만, 이를 메인 스레드에서 처리하면 많은 로드가 걸리는 작업들을 userIneractive에서 처리해서 바로 동작하는 것처럼 보이게 함.
userInitiated
클릭할 때 작업을 수행하는 것과 같은 즉각적인 결과가 필요한 작업. (ex. 저장된 문서 열기)
하지만 userIneractive 보다는 조금 오래걸릴 수 있고 유저가 어느정도 이를 인지하고 있음.
default
일반적인 작업.
utility
보통 progress bar와 함께 길게 실행되는 작업. (ex. 데이터 다운로드)
background
유저가 직접적으로 인지하지 않는 시간이 덜 중요한 작업. (ex. 동기화 및 백업)
unspecified
QoS 정보가 없음을 나타냄. 거의 사용할 일 없음.
CustomQueue의 특성
- 매개변수를 통해 직접 커스텀으로 만듭니다.
- 디폴트로 Serial 특성을 가진 Queue. 하지만 Concurrent 로 설정 가능.
- Qos 설정 가능
let customSerialQueue = DispatchQueue(label: "MyCustomSerialQueue")
let customConcurrentQueue = DispatchQueue(label: "MyCustomConcurrentQueue", qos: .background, attributes: .concurrent)
DispatchGroup
단일 단위로 모니터링하는 태스크 그룹입니다.
흠 기본 번역기로 돌리니까 무슨 말인지 모르겠네요.
DispatchGroup은 하나 이상의 작업을 그룹화하여 그룹 전체를 모니터링할 수 있게 해주는 객체라고 합니다.
주로 비동기 작업들의 작업 결과를 모니터링하는데 쓰입니다.
그룹을 사용하면 작업 집합을 집계하고 그룹의 동작을 동기화할 수 있습니다.
여러 작업 항목을 그룹에 연결하고 동일한 대기열 또는 다른 대기열에서 비동기 실행되도록 예약합니다.
모든 작업 항목 실행이 끝나면 그룹은 완료 처리기를 실행합니다.
또한 그룹의 모든 작업이 실행을 완료할 때까지 동기적으로 기다릴 수 있습니다.
흠 할말하않이네요. 코드로 알아보겠습니다.
notify
let myTaskGroup = DispatchGroup()
DispatchQueue.global().async(group: myTaskGroup) {
print("작업 1")
}
DispatchQueue.global().async(group: myTaskGroup) {
print("작업 2")
}
DispatchQueue.global().async(group: myTaskGroup) {
print("작업 3")
}
myTaskGroup.notify(queue: DispatchQueue.main) {
print("모든 작업 완료!")
}
// 작업 1
// 작업 3
// 작업 2
// 모든 작업 완료!
DispatchGroup 인스턴스를 생성하고 async메소드에서 어떤 그룹에 속하는지를 지정해줍니다. 그다음 DispatchGroup의 notify(queue: DispatchQeueue)
메소드를 통해 원하는 스레드에 모든 작업이 종료된 시점을 알려줄 수 있습니다.
wait
let myTaskGroup = DispatchGroup()
DispatchQueue.global().async(group: myTaskGroup) {
print("작업 1")
}
DispatchQueue.global().async(group: myTaskGroup) {
print("작업 2")
}
DispatchQueue.global().async(group: myTaskGroup) {
print("작업 3")
}
myTaskGroup.wait()
print("모든 작업 완료")
// 작업 2
// 작업 1
// 작업 3
// 모든 작업 완료!
wait()
메소드는 해당 그룹의 작업들이 모두 끝날 때까지 현재 스레드를 block 시킵니다. 따라서 UI작업을 하는 메인스레드에선 사용하면 안됩니다!
enter&leave
비동기 작업 내에서 또다른 비동기 작업을 수행하고 모든 작업이 완료되는 시점을 캐치하기 위해 사용할 수 있습니다.enter()
와 leave()
는 짝지어서 사용하게 됩니다.
enter()
를 호출하면 내부적으로 task reference count를 1증가시키고, leave()
는 count 를 1 감소시킵니다. 그리고 count가 0이 되면 작업이 종료되었다는 알림을 보내게 됩니다.
let myTaskGroup = DispatchGroup()
let anotherAsyncTask: () = DispatchQueue.global().async(group: myTaskGroup) {
print("또 다른 비동기 작업")
myTaskGroup.leave() // 3. task - 1
}
myTaskGroup.enter() // 1. task + 1
DispatchQueue.global().async(group: myTaskGroup) {
print("첫번째 작업")
myTaskGroup.enter() // 2. task + 1
anotherAsyncTask
print("모든 작업 완료")
myTaskGroup.leave() // 4. task - 1
}
myTaskGroup.notify(queue: .main) { // task count가 0이되면 호출
print("야호!")
}
// *실행결과*
// 또 다른 비동기 작업
// 첫번째 작업
// 모든 작업 완료
// 야호!
DispatchWorkItem
완료 핸들 또는 실행 종속성을 연결할 수 있는 방식으로 캡슐화되어 수행할 작업입니다.
쉽게 이해하자면 하나의 작업을 클로저로 묶어 보내는 대신, 캡슐화하여 보내기 위한 class입니다.
DispatchQueue.global().async(group: myTaskGroup) {
print("작업 1")
print("작업 2")
}
DispatchWorkItem
에 qos
도 지정해줄 수 있습니다.
let utilityWorkItem = DispatchWorkItem(qos: .utility) {
print("Task 시작")
print("Task 끝")
}
let defaultWorkItem = DispatchWorkItem {
print("Task 시작")
print("Task 끝")
}
이렇게 정의된 DispatchWorkItem
은 async(execute:)
라는 DispatchQueue
의 instance method를 통해 큐에 보낼 수 있습니다.
DispatchQueue.global().async(execute: utilityWorkItem)
또는 perform()
메소드를 통해 현재 스레드에서 sync 하게 동작시킬 수도 있다고 합니다.
utilityWorkItem.perform()
DispatchWorkItem의 기능
DispatchWorkItem
은 아래와 같은 두가지 기능 제공합니다.
취소 기능
DispatchWorkItem
은 cancel()
이라는 instance method를 가지고 있습니다.
let defaultWorkItem = DispatchWorkItem {
print("Task 시작")
print("Task 끝")
}
item.cancel()
이름에서부터 알 수 있듯 작업을 취소 하는거겠죠? 그런데 이 함수는 작업의 실행 여부에 따라 동작이 조금 달라집니다.
- 작업 실행 전
즉 작업이 아직 큐에 있는 상황입니다. 이때 cancel()
을 호출하면 작업이 제거됩니다.
- 작업 실행 중
실행 중인 작업에 cancel()
을 호출하는 경우, 작업이 멈추지는 않고 DispatchWorkItem
의 속성인 **inCancelled**
가 **true**
로 설정됩니다.
순서 기능
notify(queue:execute:)
라는 함수를 통해 작업 A가 끝난 후 작업 B가 특정 queue에서 실행되도록 지정할 수 있습니다.
let utilityWorkItem = DispatchWorkItem(qos: .utility) { ... }
let defaultWorkItem = DispatchWorkItem { ... }
defaultWorkItem.notify(queue: DispatchQueue.global(), execute: utilityWorkItem)
해당 함수는cancel()
과 마찬가지로 DispatchWorkItem
의 인스턴스 메소드입니다.
마지막 라인을 해석해보면defaultWorkItem
이 notify
한다 queue
에게 utilityWorkItem
을 execute
하라고
이렇게 직관적으로 읽을 수가 있습니다!
참고 링크
'iOS' 카테고리의 다른 글
GCD Sync, Async, Serial, Concurrent 조합해보기 (0) | 2024.07.30 |
---|---|
캐시 데이터 용량 표시 방식 개선하기: ByteFormatter (0) | 2024.07.29 |
Swift 6.0 접근제어자 Access Control (0) | 2024.07.02 |
Swift 메모리 관점에서의 Image (0) | 2024.06.18 |
모듈화란? with Tuist (2) | 2024.06.03 |