[iOS] 차근차근 시작하는 GCD — 8

Dispatch Group에 비동기 작업이 포함된 task 를 보내는 방법을 알아봅시다

naljin
9 min readJan 27, 2021

Previously on GCD…

지난 포스팅에서는 Dispatch Group에 대해 살펴봤는데요, Dispatch Group의 notifywait 함수를 통해 여러 스레드로 분배된 작업들의 종료 시점을 각각이 아닌 하나로 그룹지어서 한번에 파악할 수 있다고 했습니다.

이때 주목해야할 점은 저번 설명에 나온 task 의 함수(들)는 모두 동기적인 작업이었다는 것입니다.

동기적인 함수의 예시에는 print 가 있습니다

즉 아래와 같이 동기적 함수로만 구성된 Task 들을 Group으로 묶었던 것이죠.

Task 안에 담긴 함수들이 순차적으로 진행 / 종료되기 때문에, 그룹의 종료 시점을 잡는 것을 이해하기에도 큰 어려움이 없었습니다.

하지만 Task 안에 비동기 함수가 들어가면 그룹의 종료 시점을 파악하기 위해 추가적인 처리가 필요하게 되는데요, 그 이유와 방법을 이번 시간에 알아보도록 합시다.

디스패치 그룹에 비동기 작업이 포함된 task 를 보낼때 발생 가능한 문제

자, 그룹으로 묶인 Task 안에 비동기 함수(async)가 포함되어 있으면 어떤 문제가 발생할 수 있을까요?

스레드(그림에선 Thread 3)가 작업들을 수행하다가 async 함수(비동기)를 만나면, 그 안에 담긴 작업은 다른 큐로 보내서 결국 다른 스레드에서 처리하게 한다는 것을 알고 있을 거예요 . 작업을 다른 큐에 보낸 스레드는 바로 다음 작업을 수행하게 되죠.

이렇게 남아있는 작업을 마저 다 끝내면, 스레드(Thread 3)는 그룹 내 모든 Task 가 종료되었다고 인식합니다. 하지만 이때 다른 스레드(Thread 4)에 보낸 작업이 아직 진행 중이라면, 이는 잘못된 종료 시점을 인식한 상황이 됩니다.

디스패치 그룹에 비동기 작업이 포함된 task 를 보내는 방법

디스패치 그룹으로 묶이는 task 안에 비동기 작업 포함되어 있다면, 해당 작업을 보낼 때와 끝낼 때 각각 enter()leave() 메소드를 이용해야 합니다.

enter()leave()서로 짝지어 쓰이면서 task reference count를 적절히 관리하는데, enter()는 그룹 내 task 의 count를 +1 로 증가시키고, leave()는 반대로 -1 로 감소시킵니다. 짐작할 수 있듯 이 count 가 0이 되는 시점이 그룹이 끝나는 시점이겠죠?

이렇게 그림처럼 enterleave를 통해 조절되는 task reference count를 통해 적절한 그룹 종료 시점을 파악하게 됩니다.

만약 현재 dispatchGroup에 enter count 를 알고 싶다면 아래와 같은 코드를 통해 확인할 수 있습니다.

let count = dispatchGroup.debugDescription.components(separatedBy: ",").filter({$0.contains("count")}).first?.components(separatedBy: CharacterSet.decimalDigits.inverted).compactMap{Int($0)}.first

아니면 breakpoint 를 걸어서 po 명령어로 확인할 수도 있구여

(lldb) po dispatchGroup

예제

1. animate

UIViewanimate는 내부에서 비동기로 구현되어있는 비동기 함수입니다. 이는 animate에 담겨있는 작업이 각각 다른 스레드에서 처리 된다는 의미이죠. 따라서 모든 애니메이션하나의 그룹으로 묶고 마지막 종료 시점을 파악하려면 비동기적인 디스패치 그룹 처리가 필요합니다. 즉, 각 animate가 시작되고 끝나는 시점에 enter()leave()를 호출해야 해야합니다.

위의 코드는 animate를 두 번 호출하여 각각 box의 크기를 키우고, view의 배경 색상을 변경합니다. 비동기 함수이기 때문에 애니메이션은 거의 동시에 실행되고, 모든 애니메이션이 끝나면 labeltext는 “애니메이션 종료”로 변경됩니다.

주목해야할 부분은 바로 이 부분인데

빨간 박스로 표시해놨듯, animate가 시작되고 끝나는 시점에 enter()leave()를 호출하는 것을 확인할 수 있습니다. 마지막에는 notify함수를 통해 모든 애니메이션이 끝나는 시점에 메인 스레드에서 labeltext를 변경합니다.

그림으로 보면 아래 상황과 같습니다.

만약 위의 코드에서 enter()leave()를 주석 처리하면 어떻게 될까요?

이렇게 애니메이션 작업이 끝나지 않았음에도 모든 작업이 완료되었다고 인식하고, 잘못된 시점에 notification block 을 실행하게 됩니다.

2. URLSession

비슷한 예제로 URLSession 의 사례를 들 수 있는데요, URLSessionanimate와 마찬가지로 내부에서 async 로 동작합니다.

URLSession 의 작업이 시작될때 enter() 을, 끝날때 leave() 를 넣어주는 것을 확인할 수 있습니다. 참고로 defer는 현재 스코프에서 맨 마지막에 실행되어야 하는 코드를 넣어서 사용하는 함수입니다.

마무리

이렇게 Dispatch Group에 비동기 작업이 포함된 task 를 보낼때는 enter()leave()를 사용해야 함을 알고, 예제까지 살펴보았는데요! 다음 시간에는 Dispatch Work Item 으로 돌아오겠습니다! 그럼 20000!

이전 포스팅 👈🏻

다음 포스팅 👉🏻

출처

--

--