먼저 코드를 보겠습니다.
DispatchQueue.global().async { [weak self] in
NetworkLayer().getData() { response in
DispatchQueue.main.async {
self?.title = response.title
}
}
}
NetworkLayer
에서 HTTP 통신을 하고요. 클로저에 인자를 받아서 받아온 데이터 중 "title" 변수를 ViewController의 title로 할당해주고 있습니다.네트워킹할 때는, 메인쓰레드가 아닌 다른 쓰레드에서 작업을 처리하고, 마지막에는 메인스레드에서 작업을 처리하겠죠. 일련의 과정을 하나의 Task 혹으 Group 이라고 칭합니다. 부족하지만, 그림으로 표현하면 아래와 같을 겁니다.
|Main| ----------------|UI처리|--->
|Thread1| --|네트워킹처리|---------->
|Thread2| ----------------------->
여기서 DispatchGroup을 통해서 알 수 있는 것은 "언제 해당 테스트가 끝나는지" 를 알 수 있습니다. 바로 notify 메소드를 통해서 말이죠.
let animationGroup = DispatchGroup()
let animationQueue = DispatchQueue(label: "Animation")
// 첫 번째 애니메이션을 실행하고 animationGroup에 추가한다.
animationQueue.async(group: animationGroup) {
sleep(1)
print("DEBUG: 첫 번째 애니메이션 종료")
}
// 두 번째 애니메이션을 실행하고 animationGroup에 추가한다.
animationQueue.async(group: animationGroup) {
usleep(1)
print("DEBUG: 두 번째 애니메이션 종료")
}
// 세 번째 애니메이션을 실행하고 animationGroup에 추가한다.
animationQueue.async(group: animationGroup) {
usleep(1)
print("DEBUG: 세 번째 애니메이션 종료")
}
// animationGroup에 있는 모든 동작이 종료된 이후에, 코드블럭에 있는 로직을 main쓰레드에서 실행한다.
animationGroup.notify(queue: DispatchQueue.main) {
print("끗")
}
DEBUG: 첫 번째 애니메이션 종료
DEBUG: 두 번째 애니메이션 종료
DEBUG: 세 번째 애니메이션 종료
끗
animationGroup
을 생성하고 있습니다.animationQueue
라고 생성하고 있습니다.갑자기 이런 의문이 들지 않나요?
그러면, notify에서 다른 queue를 동작시켜서 테스크 간의 동기처리도 가능할 것 같은데?
네, 맞습니다. 가능합니다.
위 코드에서 notifiy 주변에 아래와 같이 코드를 수정해줍니다.
let networkingGroup = DispatchGroup()
let networkingQueue = DispatchQueue(label: "Networking", target: .global())
// animationGroup에 있는 모든 동작이 종료된 이후에, 코드블럭에 있는 로직을 main쓰레드에서 실행한다.
animationGroup.notify(queue: DispatchQueue.main) {
print("끗")
}
networkingQueue.async(group: networkingGroup) {
print("네트워킹 통신을 시작합니다. 삐빅스")
sleep(1)
}
네트워킹 통신을 시작합니다. 삐빅스
DEBUG: 첫 번째 애니메이션 종료
DEBUG: 두 번째 애니메이션 종료
DEBUG: 세 번째 애니메이션 종료
끗
let networkingGroup = DispatchGroup()
let networkingQueue = DispatchQueue(label: "Networking", target: .global())
// animationGroup에 있는 모든 동작이 종료된 이후에, 코드블럭에 있는 로직을 main쓰레드에서 실행한다.
animationGroup.notify(queue: DispatchQueue.main) {
print("끗")
networkingQueue.async(group: networkingGroup) {
print("네트워킹 통신을 시작합니다. 삐빅스")
sleep(1)
}
}
DEBUG: 첫 번째 애니메이션 종료
DEBUG: 두 번째 애니메이션 종료
DEBUG: 세 번째 애니메이션 종료
끗
네트워킹 통신을 시작합니다. 삐빅스
let networkingGroup = DispatchGroup()
let networkingQueue = DispatchQueue(label: "Networking", target: .global())
// animationGroup에 있는 모든 동작이 종료된 이후에, 코드블럭에 있는 로직을 main쓰레드에서 실행한다.
animationGroup.notify(queue: DispatchQueue.main) {
print("끗")
}
networkingQueue.async(group: networkingGroup) {
animationGroup.wait()
print("네트워킹 통신을 시작합니다. 삐빅스")
sleep(1)
}
DEBUG: 첫 번째 애니메이션 종료
DEBUG: 두 번째 애니메이션 종료
DEBUG: 세 번째 애니메이션 종료
끗
네트워킹 통신을 시작합니다. 삐빅스
animationGroup.wait()
라는 코드를 추가했습니다.networkingQueue.async(group: networkingGroup) {
animationGroup.wait()
print("네트워킹 통신을 시작합니다. 삐빅스")
sleep(1)
}
networkingQueue.async(group: networkingGroup) {
print("네트워킹 통신 중...")
}
DEBUG: 첫 번째 애니메이션 종료
DEBUG: 두 번째 애니메이션 종료
DEBUG: 세 번째 애니메이션 종료
끗
네트워킹 통신을 시작합니다. 삐빅스
네트워킹 통신 중...
wait메소드는 아래와 같이 사용할 수도 있습니다.
// 30초 동안 끝나지 않았는지 확인하는 조건문
if animationGroup.wait(timeout: .now() + 30) == .timedOut {
print("애니메이션이 끝나지 않습니다.")
}
이처럼, group이라는 개념을 통해서 언제 끝나는지 개발자가 확인할 수 있게되고, 이를 통해서 다음 Task를 이어서 처리할 수도 있습니다.
DispatchQueue에 작업을 보냄과 동시에 해당 작업이 어디에 속하는지 group을 알려준다.
(인스타그램의 해시태그같은 느낌)
DispatchGroup이 지원하는 notify 메소드를 통해서 종료 이후에 동작하도록 처리할 수 있다.
(테스크 간 동기처리)
Dispatch관련헤서 개념을 정리하면서 RxSwift가 떠올랐습니다. timeout 상태를 통해서 특정시간동안 네트워크 통신이 되지 않으면 다시 네트워크를 실행할 수 있겠죠. 그리고 그 횟수를 프로퍼티에 저장해두었다가, 특정 횟수 이상 지나면, 실패한 것으로 리턴값을 보내면, 여러가지 상황에 대해서 처리할 수 있네요. 지금 설명한 개념은 RxSwift의 retry 오퍼레이터 입니다.
RxSwift의 내부 코드는 아마도 DispatchQueue를 이리저리 볶아 놓은 형태일 겁니다. Dispatch에 관해서 제대로 모르고 RxSwift를 사용하는 사람은 아마도 없겠지만, 제가 그랬던 것 같네요. 앞으로도 Dispatch 관련글을 작성해볼 예정입니다.^^
읽어주셔서 감사합니다.