본 게시물은 앨런의 인프런 강의를 듣고 작성한 글 입니다.
1. Dispatch Group의 개념
앞서 하나의 작업이 끝나면 그 작업이 끝나는 시점에 대해 알 수 있었다. 그러나 그런 작업들은 독립적이였기 때문에 여러가지 작업을 그룹지어 하나의 그룹으로 묶어 이런 그룹으로 묶인 작업이 끝나는 시점을 알고 싶을 때 디스패치 그룹을 사용한다.
여러 작업을 그룹으로 묶인 작업이라 하면 이미지를 여러개 다운로드 받고 싶을 때 하나의 이미지를 다운로드 받는것을 하나의 작업이라고 보면 이런 작업을 여러개 묶어 여러개의 이미지를 다운로드 받는 것을 하나의 그룹을 묶어 사용할 수 있다.
3개의 그룹을 묶인 작업을 3개의 스레드에서 사용한 경우 여러 스레드에서 작업을 처리하지만 각 그룹의 작업이 끝난 시점을 알 수 있다.
let group1 = DispatchGroup()
DispatchQueue.global(qos: ).async(group: group1) { }
DispatchQueue.global(qos: ).async(group: group1) { }
DispatchQueue.global().async(group: group1) { }
group1.notify(queue: DispatchQueue.main) { [weak self] in
self?.textLabel.text = "모든 작업이 완료되었습니다."
}
위 코드에서 group1이라는 이름으로 DispatchGroup을 생성한다.
다음 큐로 보낼 때, 어떤 그룹(group1)에 넣을 것인지 정해준다. 이렇게 적어 그룹에 넣어주면 알아서 작업이 적어준 그룹(group1)에 묶인다.
따라서 두번째 단락에 있는 3줄의 코드가 같은 그룹으로 묶이고, 각각 한줄은 하나의 작업임을 알 수 있다.
마지막에 notify()를 통해 작업이 완료됨을 알린다. notify()는 모든 그룹의 작업이 끝나는 시점에 실행되는 코드이다.
이때, 매개변수로 queue를 main으로 설정해 줌으로써 이 notifiy는 main에서 실행된다.
wait()
어떤 이유로 그룹의 완료 알림에 비동기적으로 응답 할 수 없는 경우, 대신 디스패치 그룹에서 wait()을 사용할 수 있다.
이는 모든 작업이 완료 될 때까지 현재 대기열을 차단하는 동기적 방법이다. 작업이 완료될 때까지 얼마나 오래 기다릴지 기다리는 시간을 지정하는 파라미터가 필요하다. 이는 선택적으로 지정하며 적어주지 않으면 무제한으로 대기한다.
예를들어, 네트워크 통신을 하는 경우 어떠한 이유로 시간이 오래 걸릴경우 언제까지 응답을 기다릴 수는 없다. 무한정으로 기다리는 것은 사용자에게 불편을 줄 수 있기 때문에 wait()을 적절히 활용하여 화면을 제어한다.
let group1 = DispatchGroup()
DispatchQueue.global(qos: ).async(group: group1) { }
DispatchQueue.global(qos: ).async(group: group1) { }
DispatchQueue.global().async(group: group1) { }
group1.wait(timeout: DispatchTime.distantFuture)
위 코드에서 wait메서드를 이용해 기다리는 시간을 설정한다. distantFuture로 설정되어 있기 때문에 무한정으로 기다린다.
group1.wait(timeout: .now() + 60) == .timedOut {
print("작업이 60초안에 종료하지 않았습니다.")
}
이처럼 적어주면 무한정 기다리는게 아닌 지금 시간으로부터 60초간 기다린다. 60초동안 작업이 완료되지 않아 print문이 실행되어도 작업이 멈추는 것이 아니라 계속 진행되기는 한다.
실제 앱에서 메인큐(메인스레드)에서는 wait()을 사용하면 안된다. 메인큐에서 실행하게 되면 기다리는 동안 main은 block상태가 되는데 이러면 UI가 멈추기 때문이다.
2. Dispatch Group의 사용
위 코드는 동기적 함수를 실행할 때의 코드였다. 만약 클로저 내에서 비동기함수를 호출할 때 어떤 문제가 있을까?
만약 비동기 함수를 실행하면 다른 스레드에서 일하도록 작업을 보내고 바로 리턴한다. 이러한 과정에서 그룹으로 묶여있는 작업이 끝나는 시점을 잘못 인식할 수 있다.
이러한 문제를 해결하기 위해 enter()와 leave()를 활용한다.
작업을 시작할 때 enter()를 실행하고, 끝날때 leave()를 실행함으로써 두 함수의 개수가 같아질 때 진짜로 끝난 시점으로 인식하는 것이다.
queue.async(group: group1) {
group1.enter() // 입장
someAsyncMethod {
group1.leave() // 퇴장
}
}
위 코드처럼 enter메서드(입장)와 leave메서트(퇴장)의 개수가 같아지면 종료된다.
3. Dispatch workItem
Dispatch workItem이란 하나의 작업 자체를 클래스화 하여 작업을 미리 정의해놓고 사용하는 큐에 제출하기 위한 객체이다.
빈약한 취소기능과 빈약한 순서기능을 내장하고 있다.
let item1 = DispatchWorkItem(qos: .utility) {
print("task1: 출력하기")
print("task2: 출력하기")
}
let item2 = DispatchWorkItem {
print("task3: 출력하기")
print("task4: 출력하기")
}
let queue = DispatchQueue(label: "com.inflearn.serial")
queue.async(execute: item1)
queue.async(execute: item2)
위 코드와 같이 사용하며, qos를 적어주지 않으면 디폴트로 설정된다.
빈약한 취소기능
cancel()가 존재한다.
이는 작업이 아직 시작되지 않은 경우 작업이 제거되고, 작업이 실행중인 경우 inCancelled속성이 true로 설정된다.
단, 직접적으로 실행중인 작업이 멈추는 것은 아니다. 단순히 속성 설정만 하는것이며, 이를 이용해 추가적인 작업 실행여부를 설정할 수 있다.
let item1 = DispatchWorkItem(qos: .utility) {
print("task1: 출력하기")
print("task2: 출력하기")
}
item1.cancel()
let queue = DispatchQueue(label: "com.inflearn.serial")
queue.async(execute: item1)
queue.async(execute: item2)
위 코드에서 item1.cancel()을 함으로써, item1이 취소되어 아래 queue.async(execute: item1)이 실행되지 않는다.
빈약한 순서기능
notify()가 존재한다.
notify(queue: 다음 작업을 실행할큐, execute: 디스패치 아이템(어떤 작업들)) 메서드가 존재한다.
직접적으로 실행한 후 다음 실행할 작업을 지정한다.
item1.notify(queue: DispatchQueue.global(), execute: item2)
queue.async(execute: item1)
위 코드에서 item1이 실행된 후 item2가 실행된다.
4. Semaphore(세마포어)의 이해
이는 공유 리소스에 접근가능한 작업 수를 제한해야할 경우 사용한다. 예를 들어 다운로드 숫자를 제한해야 할때 10개의 스레드가 아닌 2개만 사용하도록 제한할 때 사용한다.
let semaphore = DispatchSemaphore(value: 3)
queue.async(group: group1) {
group1.enter()
semaphore.wait()
someAsyncMethod {
group.leave()
semaphore.signal()
}
}
wait()으로 일단 기다리게 하여 작업 수를 제한한다. 후에 아래에서 signal()으로 작업이 끝났으므로 다음 작업을 시작하도록 설정한다.
전체 이용가능한 값이 3일 때, wait()을 실행하면 실제 내부 작업의 값이 1줄어 이용 가능한 자원수는 2가 된다. 후에 signal()을 실행하면 1증가시켜 이용 가능한 자원수는 3이 되고 wait()과 signal()을 통해 숫자가 3(이용가능한 자원수)를 초과하지 않도록 관리한다.
*비교
enter(), leave() -> 들어가고 나가고의 숫자를 맞춰서 모든 작업이 끝났음을 알기 위한 개념
wait(), signal() (세마포어) -> 한번에 실행가능한 작업의 개수 제한
'iOS' 카테고리의 다른 글
[iOS] OAuth Login firebase와 연동하면서 알게된 점 (Apple, Google, Email, Kakao) (0) | 2022.12.23 |
---|---|
[iOS] 동시성과 관련된 문제 (Concurrency Problems) (0) | 2022.12.16 |
[iOS] 디스패치큐(GCD) 사용 시 주의해야할 사항 (0) | 2022.12.11 |
[iOS] 디스패치큐(GCD)의 종류와 특성 (메인큐, 글로벌큐, 프라이빗큐) (0) | 2022.12.07 |
[iOS] GCD와 Operation, 동기와 비동기, 직렬과 동시 (1) | 2022.12.05 |