티스토리 뷰
728x90
DispatchGroup
DispatchGroup으로 작업이 완료될 때까지 기다리거나, 완료되면 알림을 받을 수 있게 할 수 있습니다.
DispatchGroup에는 아래와 같은 메소드가 있습니다.
- wait()
- enter()
- leave()
wait()
작업이 완료되기까지 기다립니다.
wait(timeout:) 으로 시간을 지정해주지 않으면 영원히 기다리니 끝날거라는 보장이 무조건 있을 경우에만 wait() 으로 써줍니다.
enter()
작업이 시작되었음을 Group에 수동으로 알리기 위해 호출 합니다.
enter() 메소드의 호출 수와 leave() 메소드의 호출 수는 같아야 합니다.
하나라도 적거나 많으면 앱은 다운됩니다.
leave()
작업이 완료되었음을 알리기 위해 호출 합니다.
아래는 DispatchGroup을 사용한 예시 입니다.
// 1
DispatchQueue.global(qos: .userInitiated).async {
var storedError: NSError?
// 2
let downloadGroup = DispatchGroup()
for address in [
PhotoURLString.overlyAttachedGirlfriend,
PhotoURLString.successKid,
PhotoURLString.lotsOfFaces
] {
guard let url = URL(string: address) else { return }
// 3
downloadGroup.enter()
let photo = DownloadPhoto(url: url) { _, error in
storedError = error
// 4
downloadGroup.leave()
}
PhotoManager.shared.addPhoto(photo)
}
// 5
downloadGroup.wait()
// 6
DispatchQueue.main.async {
completion?(storedError)
}
}
- 동기 방식(sync)에서 wait() 메소드를 사용하면 현제 thread를 차단 합니다.
따라서, 비동기 방식(async)를 사용하여 모든 메소드를 백그라운드 대기열(queue)에 배치 시킵니다.
이렇게 하면 main thread는 차단되지 않음을 보장합니다. - 새로운 dispatch group을 만듭니다.
- enter() 메소드를 호출하여 수동적으로 작업이 시작됨을 group에게 알립니다.
※ enter() 메소드와 leave() 메소드의 호출 수 밸런스를 맞춰줍니다. - group에 작업이 종료됨을 알립니다.
- 작업이 무조건 끝날걸 알기에 wait() 메소드를 사용해줍니다.
언제 끝날지 모르는 작업일 경우 wait(timeout:)을 사용하여 시간을 정해줍니다. - 이 시점에서 모든 이미지 작업이 완료되었거나 시간이 초과됨을 알 수 있습니다.
그런다음 main queue를 다시 호출해 completion closure를 실행 합니다.
위 처럼 사용할 수도 있지만,
다른 대기열(queue)에 비동기식으로 dispatch한 다음 wait() 메소드를 사용하여 작업을 차단하는건 그닥 좋은 방법은 아닙니다.
아래는 좀더 개선된 방법입니다.
// 1
var storedError: NSError?
let downloadGroup = DispatchGroup()
for address in [
PhotoURLString.overlyAttachedGirlfriend,
PhotoURLString.successKid,
PhotoURLString.lotsOfFaces
] {
guard let url = URL(string: address) else { return }
downloadGroup.enter()
let photo = DownloadPhoto(url: url) { _, error in
storedError = error
downloadGroup.leave()
}
PhotoManager.shared.addPhoto(photo)
}
// 2
downloadGroup.notify(queue: DispatchQueue.main) {
completion?(storedError)
}
- main thread를 차단하지 않을거기 때문에 async 호출에 메소드를 넣을 필요가 없습니다.
- notify(queue:work:) 메소드는 비동기 completion closure 역할을 합니다.
그룹에 더이상 항목이 남아 있지 않을 때 실행 되며, 작업이 main queue에서 실행 되도록 예약 지정 합니다.
Exploring Concurrency Looping
위 예제에서 for loop를 사용하였는데 이를 대체할 수 있는 DispatchQueue.concurrentPerform(iterations:execute:) 가 있습니다.
진행 상황을 추적해야 하는 경우에 loop를 포함하고 있는 동시 대기열(queue)을 사용하기 적합 합니다.
var storedError: NSError?
let downloadGroup = DispatchGroup()
let addresses = [
PhotoURLString.overlyAttachedGirlfriend,
PhotoURLString.successKid,
PhotoURLString.lotsOfFaces
]
let _ = DispatchQueue.global(qos: .userInitiated)
DispatchQueue.concurrentPerform(iterations: addresses.count) { index in
let address = addresses[index]
guard let url = URL(string: address) else { return }
downloadGroup.enter()
let photo = DownloadPhoto(url: url) { _, error in
storedError = error
downloadGroup.leave()
}
PhotoManager.shared.addPhoto(photo)
}
downloadGroup.notify(queue: DispatchQueue.main) {
completion?(storedError)
}
이전 예제와 달리 for 문이 없어졌고
DispatchQueue.concurrentPerform(iterations:execute:) 가 생겨난걸 볼 수 있습니다.
사실, 위 처럼 사용할때는 적절한 stride 길이와 대량의 세트들을 반복하는 상황 일 때 유용합니다.
Canceling Dispatch Blocks
var storedError: NSError?
let downloadGroup = DispatchGroup()
var addresses = [
PhotoURLString.overlyAttachedGirlfriend,
PhotoURLString.successKid,
PhotoURLString.lotsOfFaces
]
// 1
addresses += addresses + addresses
// 2
var blocks: [DispatchWorkItem] = []
for index in 0..<addresses.count {
downloadGroup.enter()
// 3
let block = DispatchWorkItem(flags: .inheritQoS) {
let address = addresses[index]
guard let url = URL(string: address) else {
downloadGroup.leave()
return
}
let photo = DownloadPhoto(url: url) { _, error in
storedError = error
downloadGroup.leave()
}
PhotoManager.shared.addPhoto(photo)
}
blocks.append(block)
// 4
DispatchQueue.main.async(execute: block)
}
// 5
for block in blocks[3..<blocks.count] {
// 6
let cancel = Bool.random()
if cancel {
// 7
block.cancel()
// 8
downloadGroup.leave()
}
}
downloadGroup.notify(queue: DispatchQueue.main) {
completion?(storedError)
}
코드해설
더보기
- addresses 배열을 확장하여 이미지의 사본 3개를 보유 합니다.
- [DispatchWorkItem] 타입으로 된 변수 blocks 를 선언한뒤 빈 배열로 초기화 해줍니다.
- 새로운 DispatchWorkItem을 만듭니다. block이 디스패치 하는 대기열에서 QoS클래스를 상속 하기위해 flags 매개변수를 전달합니다. (질문..)
- main 대기열에 비동기적으로 block을 디스패치 합니다.
예시에서, main queue를 사용하면 serial queue이기 때문에 block을 더 쉽게 취소할 수 있습니다.
디스패치 블록을 설정하는 코드는 이미 main queue에서 실행 중 이므로, 다운로드 블록이 나중에 실행되는걸 알 수 있습니다. - blocks 배열을 slice하여 처음 세 개의 다운로드를 건너 뜁니다.
- Bool.random() 을 사용하여 무작위로 true/ false가 선택되게 합니다.
- 값이 true이면 블록을 취소합니다.
아직 대기열에 있고 실행하지 않은 블록들만 취소할 수 있습니다.
실행중에는 블록을 취소할 수 없습니다. - 디스패치 그룹에서 취소된 block을 삭제하는걸 기억합니다.
인위적인 예 이지만 디스패치 block을 사용하고 취소하는 방법을 잘 나타내 줍니다.
728x90
'iOS' 카테고리의 다른 글
[iOS] .contentMode (scaleToFill / scaleAspectFit / scaleAspectFill) (0) | 2022.03.25 |
---|---|
[Swift] 메소드 Methods (0) | 2022.03.23 |
[iOS] Delegate로 ViewController간 데이터 전달 (코드작업) (0) | 2022.03.18 |
[iOS] CollectionView - Empty state (데이터가 없을시 보여줄 화면) (0) | 2022.03.16 |
[iOS] async,sync 와 serial, concurrent의 차이? (0) | 2022.03.13 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- Swift ModernRIBs
- Swift Leetcode
- Swift joined
- swift protocol
- 원티드 프리온보딩
- Swift 프로퍼티
- Swift 알고리즘
- Swift Error Handling
- iOS error
- Swift
- Swift joined()
- ios
- Swift inout
- Swift RIBs
- swift reduce
- Swift 내림차순
- swift (programmers)
- Combine: Asynchronous Programming with Swift
- swift programmers
- CS 네트워크
- 2023년 회고
- Swift final
- removeLast()
- RTCCameraVideoCapturer
- swift property
- swift 고차함수
- RIBs tutorial
- Swift 프로그래머스
- Swift init
- Class
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함