DispatchQueue란?
DispatchQueue는 작업 항목의 실행을 관리하는 클래스다. 메인 쓰레드 혹은 백그라운드 쓰레드에서 작업을 연속적으로, 혹은 병렬적으로 실행할 수 있다. 이름에서 알 수 있듯이 Queue 자료구조의 형식을 띄고 있어, FIFO (First In First Out) 입출력 구조를 가진다.
DispatchQueue 는 다음과 같이 두 가지 종류가 있다.
- Serial Queue
- 한 작업을 끝마쳐야 다음 작업으로 넘어갈 수 있다.
- Concurrent Queue
- 여러 작업을 동시에 실행할 수 있다.
DispatchQueue 로 넣은 작업들은 시스템에 의해 관리되는 쓰레드 풀에서 실행되며, 앱의 기본 쓰레드를 의미하는 main 쓰레드를 담당하는 DispatchQueue 를 제외하곤 어떤 쓰레드에서 작업을 실행할지 보장하지 않는다.
DispatchQueue 사용법
어플리케이션에서 코드 블록을 작성해서 DispatchQueue 에 넣을 수 있다. DispatchQueue는 자체 속성에 따라 우리가 삽입한 태스크(코드 블록)들을 연속적으로, 혹은 병렬적으로 실행한다. 작업을 삽입할 때 우리는 해당 작업이 동기적으로 실행될지, 혹은 비동기적으로 실행될 지 결정할 수 있다.
만약 어떤 작업을 동기적으로 실행한다고하면, 해당 DispatchQueue 는 해당 작업이 끝날 때 까지 다음 작업을 실행하지 않는다. 반면 비동기적으로 실행한다고 하면 해당 작업을 실행하는 동안 다른 코드도 같이 실행한다.
그렇다면 이제 DispatchQueue 를 사용하는 방법을 알아보자. DispatchQueue 는 우리가 새로 만들 수도 있지만, 기본적으로 시스템에 정의된 두 가지 큐가 존재한다.
- main DispatchQueue
- 앱의 main 쓰레드에서 실행할 작업을 관리하는 DispatchQueue로, 전역적으로 사용할 수 있는 serial Queue다.
- serial Queue 이기 때문에 하나의 작업이 종료되야 다음 작업을 실행한다.
- 일반적으로 앱의 main thread 는 사용자 인터페이스를 업데이트 하는 역할을 가지고 있으므로, main DispatchQueue 에 특정 작업을 동기적으로 실행하도록 삽입한다면 데드락이 발생한다.
- Global DispatchQueue
- Concurrent DispatchQueue로, 동시에 하나 이상의 task를 실행할 수 있으며 DispatchQueue 가 관리하는 고유한 쓰레드에서 실행된다.
- 작업의 우선순위를 정할 수 있으며, 우선순위에 따라 작업의 실행 시점이 달라진다
DispatchQueue 사용법
Global DispatchQueue 는 다음과 같이 생성할 수 있고, 동기 작업과 비동기 작업을 삽입할 수 있다.
//동기 작업 예약
DispatchQueue.global().sync {
//task
}
//비동기 작업 예약
DispatchQueue.global().async {
//task
}
async 와 sync 의 차이점을 알아보기 위해 다음 코드를 보도록 하자
func syncTask(_ str : String) {
for _ in 1...100 {
print(str)
}
}
func asyncTask(_ str : String) {
for _ in 1...100 {
print(str)
}
}
DispatchQueue.global().sync {
syncTask("first task")
//task
}
DispatchQueue.global().sync {
//task
syncTask("second task")
}
동기 방식으로 작업을 순서대로 삽입했다. 이 코드를 실행하면 DispatchQueue 에 들어간 순서대로 first task 가 먼저 출력되고 난 뒤에, second task 문장이 마저 출력된다. 위 작업들을 다음과 같이 비동기적으로 삽입해보자
DispatchQueue.global().async {
syncTask("first task")
//task
}
DispatchQueue.global().async {
//task
syncTask("second task")
}
위 코드를 실행하면 출력 결과는 다음과 같이 나온다.
sync 작업과 달리, 첫 번째 작업이 다 끝나지 않아도 두 번째 작업이 동시에 실행되는 것을 알 수 있다. 실행 순서는 보장할 수 없기 때문에 항상 다른 결과가 나오게 된다.
DispatchQueue.sync {//task code} 를 사용해 동기적으로 실행될 작업을 삽입할 수 있다.
DispatchQueue.async {//task code} 를 사용해 비동기적으로 실행될 작업을 삽입할 수 있다.
Global DispatchQueue 의 우선순위
일전에 Global DispatchQueue 는 우선 순위를 가지고 있다고 하였다. GlobalDispatchQueue 의 생성 함수를 보자.
qos (Quality Of Service) 클래스를 삽입해서 DispatchQueue 를 생성할 수 있다. 여기서 qos 는 기본적으로 default 클래스를 매개변수로 받으므로, DispatchQueue.global() 함수는 default 우선 순위를 가지는 DispatchQueue 를 반환한다고 할 수 있다.
QOS
쉽게 말하면 우선순위를 뜻하는 클래스로, 이 값에 따라 작업의 실행순위가 결정된다.
qos 는 위와 같은 목록이 있으며, 우선순위가 내림차순으로 정렬되있다. userInteractive 케이스의 경우 가장 높은 우선순위를 가지고 있으며, 다른 어떤 우선순위를 가진 작업보다 우선적으로 실행된다. background 케이스의 경우 가장 낮은 우선순위를 가지고 있으며, 당연히 스케줄링에서 뒤로 밀리게 된다.
수행할 작업에 적절한 qos 를 지정해준다면, 배터리 효율이 좋아지고 반응성이 뛰어난 앱을 만들 수 있다.
직접 DispatchQueue 를 만들기
시스템에 이미 정의된 global, main DispatchQueue 말고도 나만의 DispatchQueue 를 생성할 수 있다. DispatchQueue 의 생성자를 보자
label 은 DispatchQueue 의 이름을 뜻하고, 나머지는 매개변수 기본 값이 설정되 있는 것을 볼 수 있다. 여기서 중요한 게 바로 attribute 매개변수로, 해당 DisptachQueue 가 serial queue 가 될 것인지 concurrent queue 가 될 것인지를 정해준다.
//attribute 를 지정하지 않으면 기본적으로 serial 큐로 생성
let jonghoSerialQueue = DispatchQueue(label: "JongHoQueue")
//attribute에 concurrent 를 넣어야 concurrent queue 로 생성
let jonghoConcurrentQueue =
DispatchQueue(label: "JongHoConcurrentQueue",attributes: .concurrent)
나만의 DispatchQueue 에도 다음과 같이 작업을 추가할 수 있다.
jonghoSerialQueue.async {
asyncTask("Hihi")
}
sync 와 async 의 차이
sync 작업을 추가하면, 해당 작업이 끝날 때 까지 나머지 코드의 실행이 멈추게 된다. 다음 코드를 보자
DispatchQueue.global().sync {
for i in 1...10 {
print(i)
}
print("task finish")
}
for i in 11...15 {
print(i)
}
sync 작업이기 때문에, 해당 작업이 끝날 때 까지 나머지 코드를 실행하지 않는다.
이는 serial Queue 에도 마찬가지이다.
DispatchQueue(label: "JongHoSerialQueue").sync {
for i in 1...10 {
print(i)
}
print("task finish")
}
for i in 11...15 {
print(i)
}
이번엔 async 작업을 추가해보자. 먼저 concurrent queue 인 global queue 에 작업을 넣어서 테스트해보자.
DispatchQueue.global().async {
for _ in 1...100 {
print("GLOBAL ASYNC")
}
}
DispatchQueue.global().async {
for _ in 1...100 {
print("GLOBAL ASYNC 2!!!")
}
}
for _ in 1...100 {
print("JUST CODE")
}
global dispatchqueue 는 concurrent queue 이고, 작업 자체도 비동기적으로 설정했기 때문에 코드의 수행 결과는 매번 달라진다.
그렇다면 이번엔 serial DispatchQueue 에 async 로 작업을 수행해보자
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async {
for _ in 1...100 {
print("GLOBAL ASYNC")
}
}
serialQueue.async {
for _ in 1...100 {
print("GLOBAL ASYNC 2!!!")
}
}
for _ in 1...100 {
print("JUST CODE")
}
async 이기 때문에 결과는 매번 다르지만, serail 한 queue 이기 때문에 내부적으로 관리하는 작업은 순서대로 실행되는 것을 확인할 수 있다.
GLOBAL ASYNC
JUST CODE
GLOBAL ASYNC
GLOBAL ASYNC
GLOBAL ASYNC
GLOBAL ASYNC
GLOBAL ASYNC 2!!!
GLOBAL ASYNC 2!!!
GLOBAL ASYNC 2!!!
GLOBAL ASYNC 2!!!
GLOBAL ASYNC 2!!!
GLOBAL ASYNC 2!!!
GLOBAL ASYNC 2!!!
GLOBAL ASYNC 2!!!
...
'Swift' 카테고리의 다른 글
Swift Collection 타입을 안전하고, 효율적으로 사용해보자 (0) | 2022.12.10 |
---|---|
weak와 unowned의 차이 (ARC) (0) | 2022.11.08 |
Swift - ARC (0) | 2022.02.22 |
Swift - where 절 (0) | 2022.02.21 |
Swift - 타입 중첩 (0) | 2022.02.21 |
Comment