async/await이 최신기술이지만 GCD도 정리해두겠습니다.
제가 지금 개발하는 서비스에서는 async/await 쓰려면 한참 남았거든요..
요새 나오는 핸드폰들은 대부분 6코어를 탑재합니다. 적어도 12개 이상의 쓰레드가 있다는 것이죠.
하지만? 여기서 메인 쓰레드 1개만 무진장 써버리면 아무리 6코어가 있는 핸드폰이어도 버벅일겁니다.
결국 동시성 프로그래밍이란 수행해야할 코드 조각(Task)를 분산 시키자는겁니다.
어디로? 메인쓰레드에서 다른 쓰레드로!
고맙게도 iOS는 Task를 작업 대기 행렬(Queue)로 보내기만하면 알아서 쓰레드 관리를 해줍니다🥹
iOS에서 작업 대기 행렬은 크게
이 2개의 Queue에 Task를 보내면 정말 iOS에서 쓰레드 숫자, 관리 등을 다 해줍니다.
GCD는 Grand Cental Dispatch Queue를 말합니다.
GCD를 코드로 나타내기 전에 단어 뜻을 알면 이해하기 쉬운데,
Dispatch는 보내다
라는 뜻입니다.
DispatchQueue.global().async {
Task1
Task2
Task3
}
DispatchQueue + global() + async { ... Task ... }
(큐에 보내다
) (글로벌 큐에
) (비동기적으로
) (Task를
)
뜻을아니까 쉽죠?
GCD를 사용하는법은 이겁니다.
DispatchQueue + Queue 종류 + 프로세스 수행 순서 + 실제 실행할 코드 블럭
기본적으로
DispatchQueue.global().async {
Task1
Task2
Task3
}
DispatchQueue.global().async {
Task4
Task5
Task6
}
한 Task 블럭안에 들어가있는 세부 Task들은 순서대로 실행되지만,
다른 Task 블럭안에 들어가있는 세부 Task들이 async하게 들어가면 순서가 보장되지 않습니다.
두 종류가 있다고 했는데,
DispatchQueue는 언제 쓸까요?
DispatchQueue에는 3가지 종류가 있습니다.
각 큐마다 특성이 다르기 때문에 특성, 원하는 작업에 따라 Queue에 Task를 보내면 된다.
일단! Queue에 Task를 보내면 iOS가 알아서 해줍니다.
각 큐의 특성에 대해 알아보겠습니다.
메인쓰레드
를 의미합니다.
유일하고, 1개만 존재합니다.
Serial(직렬) Queue 입니다.
(= 👩💻 메인스레드에서 분산 처리 시킨 작업을 다른 1개의 쓰레드에서만 수행하는 큐)
DispatchQueue.main.async {
Task
}
큐의 서비스 품질(quality of service, qos)
에 따라 6종류가 있습니다.
기본이 Concurrent(병렬) Queue 입니다.
DispatchQueue.global(qos: .userInteractive).async {
Task
}
코드로 생성할 땐 global() 안에 qos를 정해주면 됩니다.
중요도순
으로 정리하면 다음과 같습니다.
qos | 어떤 작업에 적합한지? | 소요 시간 |
---|---|---|
.userInteractive | 유저와 직접적 인터렉션: UI업데이트, 애니메이션, UI 관련 어떤것이든 지금 당장 수행되지 않으면 사용자가 어색함을 느끼는 작업 | 거의 즉시 |
.userInitiated | 유저가 즉시 필요하긴 하지만, 비동기적으로 처리된 작업 (ex. 앱 내 pdf 파일 열기, 로컬 DB 읽기) | 몇 초 |
.default | 일반적인 작업 | - |
.background | 유저가 직접적으로 인지하지 않고, 시간이 중요하지 않은 작업 (ex. 데이터 미리 가져오기, DB 유지보수, 원격 서버 동기화 및 백업 수행) | 몇 분 이상 속도보다는 에너지 효율성 중시 |
.unspecified | legacy API 지원 | - |
qos는 Queue에 대한 설정
입니다.
qos를 지정하면 무슨 의미가 있을까요?
iOS가 알아서 우선적으로 중요한 일임을 인지하고, 쓰레드에 우선순위를 매깁니다.
큐에 더 여러개의 쓰레드를 배치하고 배터리를 더 집중해서 사용하도록 합니다.
👩💻 qos는 queue뿐 아니라 Task에도 세팅할 수 있습니다.
DispatchQueue.global.async(qos: .userInteractive) { ... Task ... }
커스텀으로 생성할 수 있습니다.
디폴트가 Serial Queue 입니다. (Concurrent로 설정도 가능합니다)
label
, attributes
를 정해주면 됩니다.
let queue = DispatchQueue(label: "abcde")
let queue2 = DispatchQueue(labe: "qewtqr", attributes: .concurrent)
Qos 설정할 수 있긴한데, iOS가 알아서 추론해줍니다.
여기까지 GCD를 간단히 보았는데요,
갑자기 궁금증이 생겼습니다.
하나의 개념을 공부하다보면 갑자기 기존에 잘 써오던게 갑자기 이해가 안되는 그런 현상이..;;
왜 URLSession을 통할 땐 GCD를 통해 하지 않을까요..?
그건 바로 URLSession이 비동기적으로 작동하도록 설계 되었기 때문입니다.
비동기적으로 작동하도록 설계됐다는건 내부적으로 별도의 백그라운드 스레드
에서 실행 시킨다는 것 입니다.
그래서 URLSession 통신 코드는 GCD를 사용하지 않아도 되었던 것이죠..
그래서 completionHandler에서 데이터를 받고 UI 업데이트 해줄 때 명시적으로 DispatchQueue.main.async { UI 업데이트 }를 해주는것입니다.
UI관련 작업은 반드시! 메인 큐에서 처리
해야 합니다. (DispatchQueue.main)
DispatchQueue.global().async {
// 이미지 다운로드1
// 이미지 다운로드2
DispatchQueue.main.async {
self.imageView.image = 다운로드 받은 이미지
}
}
URLSession.shared.dataTask(with: url) {
// 이미지 다운로드1
// 이미지 다운로드2
DispatchQueue.main.async {
self.imageView.image = 다운로드 받은 이미지
}
}
이것을 확장이라고 하는데...
예를들어
// 1번
DispatchQueue.global().async {
// 이미지 다운로드1
// 이미지 다운로드2
// 2번
DispatchQueue.main.async {
self.imageView.image = 다운로드 받은 이미지
}
}
1번 작업이 2번 글로벌 큐의 3번 쓰레드에서 실행되고 있더라도,
2번 작업이 메인 큐의 2번 쓰레드에서 실행된다는 것이다.
1️⃣
메인 큐 -> 다른 큐로 Task를 보낼 때 sync 메서드를 사용하면 절!대! 안된다.
왜?
메인 큐 == Serial Queue다.
메인 큐에서 다른 쓰레드로 작업을 보내는게 다른 1개의 쓰레드 밖에 없다는 소리.
근데 sync란? 프로세스의 실행 순서 보장개념에서 현재 Task가 끝나면 다음 Task를 실행시킨다는 개념이다.
즉,
DispatchQueue.main.sync { .. }
이 코드는 메인 쓰레드가 다른 쓰레드에 보낸 Task가 끝날 때 까지 기다린다는 뜻이다.
메인 쓰레드는..? UI쓰레드... UI는..? 사용자가 보는 화면..! 멈추면 안돽!
2️⃣
현재 큐에서 현재 큐로 "동기적으로" 작업을 보내서는 안된다.
(운 나쁜 경우에)현재 큐를 블락하는 동시에 다시 현재 큐에 접근하기 때문에 교착상황(DeadLock)
이 발생한다.
// 큐에 보냈더니 작업이 2번쓰레드에 배치
DispatchQueue.global().async {
..
// 헉 작업이 2번쓰레드로 배치됟낟면 교착상태가된다.
// 하지만 다른 쓰레드로 배치하면 교착상황이 발생하지 않는다.
DispatchQueue.global().sync {
...
}
}
하지만 코드를 저렇게 짜진않고...
보통 뷰 컨트롤러안에서 GCD를 보내고, 그 안에서 어떤 B 뷰컨트롤러가 다시 GCD를 sync로 보내는 경우다.
요기까지 GCD 베이직이었씁니다.
강의를 듣는데로 더 정리해보겠습니다!