GCD 란?
GCD 는 iOS 에서 멀티코어 프로세서에 코드를 동시에 실행시키게 해주는 프레임워크이다.
- GCD는 iOS 에서 멀티 스레드 환경에서 다수의 스레드에 작업을 적절히 분배시키는 방법
- GCD 에서 사용하는 Queue 가 Dispatch Queue
- 프로그래머가 Dispatch Queue 에 작업을 보내면 그에 따라 스레드를 적절히 생성해서 실행하고 작업이 종료되면 스레드를 제거한다.
사용 예
DispatchQueue.global().async {
}
- DispatchQueue: iOS에서 동시성 프로그래밍을 돕기 위해 제공하는 queue
- global: DispatchQueue의 종류
- async: 비동기
비동기, async / 동기, sync (메인스레드가 기다리냐 안기다리냐!)
비동기, async
DispatchQueue.global().async {
}
- global dispatch queue 에 작업을 비동기 적으로 보낸다!
- 메인 스레드에서 global dispatch queue 에 task1을 보낸 후 해당 작업이 끝나는 여부에 관계 없이 바로 다음 작업을 시작한다.
- task1 이 끝나는 시점은 swift 에서 클로저를 통해서 알려준다.
completionHandler
또는 completion
동기, sync
DispatchQueue.global().sync {
}
- 메인 스레드가 task1을 dispatch queue에 보내고 task1이 끝날 때 까지 기다린 후에 다음 작업을 수행한다.
- 실제로는 동기적으로 코드를 짜더라도 메인스레드에서 작업을 수행한다.
비동기 처리가 필요한 이유
URLSession.shared.dataTask(with: request) { (data, response, error) in
}
- 시간이 절약 됨!
- 시간이 많이 드는 작업의 대부분은 서버와의 통신이기 때문에 네트워크와 관련된 작업들은 내부적으로 비동기적으로 구현이 되어있다!
URLSession
-> 내부적으로는 다른 스레드를 사용하며 비동기적으로 구현이 되어 있음!
Serial/Concurrent (Queue 의 특성)
Serial
보통 메인 스레드에서 분산 처리 시킨 작업을 다른 한개의 스레드에서 처리하도록 함
- GCD는 serial queue에 담긴 작업들은 오직 하나의 스레드에 분배한다.
- 모든 작업이 이전 작업이 끝나기를 기다렸다가 하나씩 실행된다.
- 각 task의 시작과 종료 순서에 대한 예측이 용이함
- 실행 순서가 중요한 작업들을 처리하고자 할 때 사용할 수 잇다.
Concurrent
보통 메인 스레드에서 분산 처리 시킨 작업을 다른 여러개의 스레드에서 처리하도록 함
- GCD는 concurrent queue 에 담긴 작업들은 여러 스레드에 분배한다.
- 각 작업들이 끝나는 순서를 알 수 없다.
- 작업들이 끝나는 순서보다 빠른 수행이 중요할 때
- ex) 테이블 뷰에서 각 셀에 이미지를 띄울 때 순서대로 띄워지는 것 보다는 네트워크에서 받아오는 작업이 끝나는대로 빠르게 보여지는 것이 더 유리!
async/sync - serial/concurrent
- async/sync
- 메인 스레드에서 작업을 보내는 시점에서 작업이 끝나기를 기다릴지 말지에 대한 여부
- serial/concurrent
- Queue(대기열)에 있는 작업들을 GCD 가 여러개의 스레드로 보낼 것인지 한개의 스레드로 보낼 것인지에 대한 여부
SerialQueue.sync
메인 스레드의 작업 흐름이 queue에 넘긴 태스크가 끝날때까지 멈춰있고(sync), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 같은 스레드에 보내지기 때문에 해당 작업들이 모두 끝나야 실행 (Serial Queue)
ConcurrentQueue.sync
메인 스레드의 작업 흐름이 queue에 넘긴 태스크가 끝날때까지 멈춰있고(sync), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 다른 스레드에 보내질 수 있기 때문에 해당 작업들이 모두 끝나지 않아도 실행 (Concurrent Queue)
SerialQueue.async
메인 스레드의 작업 흐름이 태스크를 queue에 넘기자마자 반환되고 (async), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 같은 스레드에 보내지기 때문에 해당 작업들이 모두 끝나야 실행 (Serial Queue)
ConcurrentQueue.async
메인 스레드의 작업 흐름이 태스크를 queue에 넘기자마자 반환되고 (async), 넘겨진 task는 queue에 먼저 담겨있던 작업들과 다른 스레드에 보내질 수 있기 때문에 해당 작업들이 모두 끝나지 않아도 실행 (Concurrent Queue)
Dispatch Queue 의 종류
실제로 iOS 에서 제공하는 Dispatch Queue의 종류는 세가지이다. Queue의 종류에 따라 Queue의 특성이 다르기 때문에 작업의 특성, 원하는 일 처리 방식에 따라 Queue를 선택해야 한다.
Main Queue
- 오직 한개만 존재하며 Serial 특성을 가지고 있음
- Main Queue에 할당된 task는 메인 스레드에서 처리 (UI 업데이트 내용 처리)
- 하지만 실제로 아래와 같이 작성하면 에러 발생!
DispatchQueue.main.sync {
}
- 메인 큐에서 task는 메인 스레드로만 보냄
- 메인 스레드는 오직 한개 밖에 없음
- 따라서 메인 큐에 있는 task들을 다른 곳에 분산시킬래도 분산 시킬만한데가 없음
- 큐의 특성은 무조건 Serial이 됨 (분산은 Concurrent의 특징)
Global Queue
- Concurrent 특성을 가진 Queue
- QoS (Quality of Service) 에 따라 알맞는 스레드에 나누어 분배된다.
DispatchQueue.global(qos: .utility).async {
}
QoS 종류
userInteractive
- 사용자와 직접 상호작용하는 UI업데이트, 애니메이션 등의 작업
- 메인 스레드에서 직렬적으로 처리하면 시간이 오래 걸리는 작업들을 userInteractive 에서 처리해 바로 동작하는 것처럼 보이게 함
userInitiated
- 즉각적인 결과가 필요한 작업
- userInteractive 보다는 조금 오래 걸릴 수 있고 유저가 어느정도 이를 인지하고 있다.
default
utility
- 데이터 다운로드와 같이 보통 progress bar와 함께 길게 진행되는 작업
unspecified
- QoS 정보가 없음을 나타냄
- 거의 사용할 일 없음
설정된 qos 에 따를 각 queue에 작업들이 분배된다.
- 실제로 우선순위가 더 높은 Queue 의 작업들에 더 많은 스레드를 배치한다.
Custom Queue
- 프로그래머가 커스텀으로 만드는 큐
- 디폴트로 Serial 특성을 가지는 Queue, Concurrent 로 설정 가능하다.
- Qos 설정 가능
let customQueue = DispatchQueue(label : "custom", qos: .background, attribute: .concurrent)
GCD의 필요성
기존에 멀티 스레드를 사용하려면 개발자가 직접 스레드를 생성하고 관리 했어야 했지만 GCD를 사용하면 스레드 생성, 유지, 삭제등에 개발자가 신경쓰고 개입하지 않아도 된다.
GCD 사용 시 주의 사항
1. UI 업데이트는 메인 스레드에서 처리 해야 함
- 메인 스레드는 UI 그리는 일을 담당
- iOS 를 비롯한 모든 OS 에서 마찬가지
- 이미지 등을 global 에서 다운 받아오더라도, 해당 데이터를 UIImageView에 넣어서 UI 업데이트 시켜주는 작업은 main에서 진행해야 함
URLSession
opertation queue이고, 모든것이 background thread에서 동작하기 때문에 imageView 에 데이터를 넣어서 화면을 업데이트 시키는 작업은 명시적으로 DispatchQueue.main
에서 진행해주어야 함!
2. 메인 큐에서 다른 큐로 작업을 보낼 때 sync 를 사용해선 안됨
sync
로 보내게 되면 작업을 보내고 완료될 때 까지 기다린다는 의미!
- 메인 스레드는 UI 를 업데이트 해주어야 하는 역할, 기다릴 수 없고 기다려서는 안됨!
- 메인스레드 에서는 항상 비동기
async
로 작업을 보내야 함!
3. 현재 작업중인 큐와 동일한 큐에 sync로 작업을 보내선 안됨
- 각 큐에서 사용하는 스레드는 정해져 있음
- 같은 큐에 작업을 보낸다면 동일한 스레드에 배치될 수 있고, 이 때
sync
로 보낸다면 동일한 스레드에 보내진 작업이 끝날 때 까지 보낸 작업도 수행될 수 없는 데드락 상태가 발생할 수 있음!!
- TaskA 수행 중 TaskB를 동일한 큐로 sync 로 보냄
- Thread2 에서 TaskA 수행 도중 TaskB 가 동일한 큐에 보내짐
- TaskB 역시 동일한 Thread2 에 배치 된다면?!
DispatchQueue.global().async {
DispatchQueue.global().sync
}
DispatchQueue.global(qos: .utility).async {
DispatchQueue.global().sync
}
- qos 가 다르게 설정된다면 동일한 스레드에 배치되지 않음!
4. 메인 스레드에서 DispatchQueue.main.sync 를 사용해선 안됨
- 메인스레드에서 작업중
DispatchQueue.main.sync
사용시 동일한 메인 큐로 보내짐
sync
로 보냈기 때문에 반드시 데드락 발생!
5. 객체에 대한 캡처에 주의해야 함
- task 를 큐에 보낸다는 것은 클로저를 보낸다는 것을 의미!
- 객체에 대한 캡처 현상 발생할 수 있게 되고, 자칫하면 retain cycle이 생길수도 있음
weak self
를 써서 방지할 수 있음
Reference
[iOS] 차근차근 시작하는 GCD — 1