[iOS] 차근차근 시작하는 GCD — 8
Previously on GCD…
지난 포스팅에서는 Dispatch Group에 대해 살펴봤는데요, Dispatch Group의 notify
나 wait
함수를 통해 여러 스레드로 분배된 작업들의 종료 시점을 각각이 아닌 하나로 그룹지어서 한번에 파악할 수 있다고 했습니다.
이때 주목해야할 점은 저번 설명에 나온 task 의 함수(들)는 모두 동기적인 작업이었다는 것입니다.
즉 아래와 같이 동기적 함수로만 구성된 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이 되는 시점이 그룹이 끝나는 시점이겠죠?
이렇게 그림처럼 enter
와 leave
를 통해 조절되는 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
UIView
의 animate
는 내부에서 비동기로 구현되어있는 비동기 함수입니다. 이는 animate
에 담겨있는 작업이 각각 다른 스레드에서 처리 된다는 의미이죠. 따라서 모든 애니메이션을 하나의 그룹으로 묶고 마지막 종료 시점을 파악하려면 비동기적인 디스패치 그룹 처리가 필요합니다. 즉, 각 animate
가 시작되고 끝나는 시점에 enter()
와 leave()
를 호출해야 해야합니다.
위의 코드는 animate
를 두 번 호출하여 각각 box
의 크기를 키우고, view
의 배경 색상을 변경합니다. 비동기 함수이기 때문에 애니메이션은 거의 동시에 실행되고, 모든 애니메이션이 끝나면 label
의 text
는 “애니메이션 종료”로 변경됩니다.
주목해야할 부분은 바로 이 부분인데
빨간 박스로 표시해놨듯, animate
가 시작되고 끝나는 시점에 enter()
와 leave()
를 호출하는 것을 확인할 수 있습니다. 마지막에는 notify
함수를 통해 모든 애니메이션이 끝나는 시점에 메인 스레드에서 label
의 text
를 변경합니다.
그림으로 보면 아래 상황과 같습니다.
만약 위의 코드에서 enter()
와 leave()
를 주석 처리하면 어떻게 될까요?
이렇게 애니메이션 작업이 끝나지 않았음에도 모든 작업이 완료되었다고 인식하고, 잘못된 시점에 notification block 을 실행하게 됩니다.
2. URLSession
비슷한 예제로 URLSession
의 사례를 들 수 있는데요, URLSession
은 animate
와 마찬가지로 내부에서 async 로 동작합니다.
URLSession
의 작업이 시작될때 enter()
을, 끝날때 leave()
를 넣어주는 것을 확인할 수 있습니다. 참고로 defer
는 현재 스코프에서 맨 마지막에 실행되어야 하는 코드를 넣어서 사용하는 함수입니다.
마무리
이렇게 Dispatch Group에 비동기 작업이 포함된 task 를 보낼때는 enter()
와 leave()
를 사용해야 함을 알고, 예제까지 살펴보았는데요! 다음 시간에는 Dispatch Work Item 으로 돌아오겠습니다! 그럼 20000!