[iOS] GCD - 1

Hyunndy·2023년 3월 7일
1

iOS-Concurrency

목록 보기
4/4

🐸

async/await이 최신기술이지만 GCD도 정리해두겠습니다.
제가 지금 개발하는 서비스에서는 async/await 쓰려면 한참 남았거든요..


Concurrency 프로그래밍이 필요한 이유

요새 나오는 핸드폰들은 대부분 6코어를 탑재합니다. 적어도 12개 이상의 쓰레드가 있다는 것이죠.
하지만? 여기서 메인 쓰레드 1개만 무진장 써버리면 아무리 6코어가 있는 핸드폰이어도 버벅일겁니다.

결국 동시성 프로그래밍이란 수행해야할 코드 조각(Task)를 분산 시키자는겁니다.
어디로? 메인쓰레드에서 다른 쓰레드로!

고맙게도 iOS는 Task를 작업 대기 행렬(Queue)로 보내기만하면 알아서 쓰레드 관리를 해줍니다🥹

Queue (작업 대기 행렬)

iOS에서 작업 대기 행렬은 크게

  • GCD Queue
  • Operaion Queue
    2종류가 있습니다.

이 2개의 Queue에 Task를 보내면 정말 iOS에서 쓰레드 숫자, 관리 등을 다 해줍니다.

GCD

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는 언제 쓸까요?

  • 비교적 간단한 작업
  • 메서드 위주 Task
    를 수행할 때 사용합니다.

종류

DispatchQueue에는 3가지 종류가 있습니다.

  • 메인 큐
  • 글로벌 큐
  • 프라이빗(커스텀) 큐

각 큐마다 특성이 다르기 때문에 특성, 원하는 작업에 따라 Queue에 Task를 보내면 된다.
일단! Queue에 Task를 보내면 iOS가 알아서 해줍니다.

각 큐의 특성에 대해 알아보겠습니다.

메인 큐

메인쓰레드를 의미합니다.
유일하고, 1개만 존재합니다.
Serial(직렬) Queue 입니다.
(= 👩‍💻 메인스레드에서 분산 처리 시킨 작업을 다른 1개의 쓰레드에서만 수행하는 큐)

DispatchQueue.main.async {
	Task
}

글로벌 큐

큐의 서비스 품질(quality of service, qos)에 따라 6종류가 있습니다.
기본이 Concurrent(병렬) Queue 입니다.

Qos

DispatchQueue.global(qos: .userInteractive).async {
	Task
}

코드로 생성할 땐 global() 안에 qos를 정해주면 됩니다.

중요도순 으로 정리하면 다음과 같습니다.

qos어떤 작업에 적합한지?소요 시간
.userInteractive유저와 직접적 인터렉션:
UI업데이트, 애니메이션, UI 관련 어떤것이든
지금 당장 수행되지 않으면 사용자가 어색함을 느끼는 작업
거의 즉시
.userInitiated유저가 즉시 필요하긴 하지만, 비동기적으로 처리된 작업
(ex. 앱 내 pdf 파일 열기, 로컬 DB 읽기)
몇 초
.default일반적인 작업-
.background유저가 직접적으로 인지하지 않고, 시간이 중요하지 않은 작업
(ex. 데이터 미리 가져오기, DB 유지보수, 원격 서버 동기화 및 백업 수행)
몇 분 이상
속도보다는 에너지 효율성 중시
.unspecifiedlegacy 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가 알아서 추론해줍니다.


URLSession과 GCD

여기까지 GCD를 간단히 보았는데요,
갑자기 궁금증이 생겼습니다.
하나의 개념을 공부하다보면 갑자기 기존에 잘 써오던게 갑자기 이해가 안되는 그런 현상이..;;

왜 URLSession을 통할 땐 GCD를 통해 하지 않을까요..?
그건 바로 URLSession이 비동기적으로 작동하도록 설계 되었기 때문입니다.
비동기적으로 작동하도록 설계됐다는건 내부적으로 별도의 백그라운드 스레드에서 실행 시킨다는 것 입니다.

그래서 URLSession 통신 코드는 GCD를 사용하지 않아도 되었던 것이죠..
그래서 completionHandler에서 데이터를 받고 UI 업데이트 해줄 때 명시적으로 DispatchQueue.main.async { UI 업데이트 }를 해주는것입니다.


GCD 사용 시 주의해야할 점

반드시 메인 큐에서 처리해야 하는 작업

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 = 다운로드 받은 이미지
    }
}

DispatchQueue Task 안에 또 DispatchQueue Task 호출하는 것

이것을 확장이라고 하는데...
예를들어


// 1번
DispatchQueue.global().async {
	// 이미지 다운로드1
    // 이미지 다운로드2
    
    // 2번
    DispatchQueue.main.async {
    	self.imageView.image = 다운로드 받은 이미지
	}
}

1번 작업이 2번 글로벌 큐의 3번 쓰레드에서 실행되고 있더라도,
2번 작업이 메인 큐의 2번 쓰레드에서 실행된다는 것이다.

sync 메서드에 대한 주의사항

🙅‍♀️ 절대 하면 안되는 행위

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 베이직이었씁니다.
강의를 듣는데로 더 정리해보겠습니다!

profile
https://hyunndyblog.tistory.com/163 티스토리에서 이사 중

0개의 댓글