Dispatch Group

라무·2023년 8월 9일

선수 지식

  • GCD는 ios에서 thread pool(생성된 thread)를 관리하는 개념 → 즉, GCD가 스레드를 적절하게 생성해서 분배해준다
  • Dispatch Queue: Thread safe하게 Thread를 관리하는 도구
  • Queue의 종류에는 크게 두 가지가 존재함
    • Serial Queue: Main Queue → 하나의 Thread로 작업 처리
    • Concurrent Queue: Global Queue → 여러개의 Thread로 작업처리
  • Task의 종류에는 크게 두 가지 존재
    • sync: Task 응답을 기다림
    • async: Task 응답을 기다리지 않음
  • QoS: 우선순위 결정

Dispatch Group 기본개념

💡 단일 단위로 모니터링하는 작업 그룹
  • 여러 스레드로 분배된 작업들이 끝나는 시점을 각각 파악하는 것이 아니라, 작업들을 하나로 그룹지어서 한번에 파악하고 싶을 때 사용한다
  • 작업들을 하나의 그룹으로 묶고 그 그룹의 마지막 시점을 파악하는 것 → 즉, 그룹으로 묶인 작업의 마지막 시점을 파악하는 것

핵심개념

  • enter(): Dispatch Group에 들어가면서, task + 1 → group에 task를 포함시킴
  • leave(): Dispatch Group에서 나오며, task - 1 → group에 해당 task가 완료되었음을 알린다
  • notify(): task가 0이 되었을 때 → 모든 task가 실행되었을 경우 알림을 받음
    • async하게 동작한다
  • wait(): 해당 그룹의 모든 작업이 완료될때까지 현재 스레드를 block시키는 것
    • 그룹 작업이 다 끝나야지만 다음 작업을 할 수 있는 상황에서, 어떤 이유로 그룹의 완료 알림에 비동기적으로 응답할 수 없는 경우에 사용
    • sync하게 동작한다

Dispatch Group 사용방법(enter, leave 사용)

    1. DispatchGroup을 생성한다
  1. enter를 통해 Dispatch Group에 들어가면서, task를 넣어준다
  2. async안에 코드를 작성한다
    1. async안에 leave를 통해서 작업이 끝나면 group에서 나오고 그룹에 나옴을 알려줌
  3. notify를 통해서 작업이 끝났음을 알림받는다
let group = DispatchGroup()

group.enter()

DispatchQeue.global().async { 
		for i in 1...5 {
			print("\(i)⭐️")
		}
		group.leave()
}
	
group.enter()

DispatchQeue.global().async { 
		for i in 100...105 {
			print("\(i)🔥")
		}
		group.leave()
}

DispatchQueue.notify(queue: group) {
		print("task end")
}

Dispatch Group 사용방법(enter, leave 사용x)

  1. DispatchGroup 생성하기
  2. async 메소드로 task를 보낼 때, group 파라미터에 1번에서 생성한 그룹 추가
    • 즉, 큐로 보낼때 어떤 그룹에 넣을 건지 정해준다
    • 이때 task를 다른 큐로 보내더라도 같은 그룹으로 지정할 수 있다
  3. notify(queue: )를 통해서 notification block(그룹으로 묶인 모든 작업이 끝났을 때 실행될 작업)을 넘김
    • 이때 queue 파라미터는 해당 작업을 어느 곳에서 실행할지 정한다
    • 만약 group1로 묶인 모든 task가 다 끝났거나, 아예 들어오지 않아 비어있는 상태라면 notification block은 바로 실행된다
let group = DispatchGroup()
DispatchQueue.global().async(group: group1) { 
		//task 
}

//다른 큐로 보내지만 같은 그룹으로 지정
DispatchQueue.global(qos: .utility).async(group: group1) {
		//task
}
DispatchQueue.global().async(group: group1) {
		//task
}
//main queue 에서 notification block 을 실행한다 
group1.notify(queue: .main) { [weak self] in
  self?.myLabel.text = "Group1으로 묶인 모든 작업이 끝났습니다."
}

예제

firebase에서 Reference 배열로 처리한 문서 가져오기

//DataType 정의

struct DemoReview: Codable {
    var menuId : String?
    var menuName : String?
    var reviewContent: String?
    var reviewId : String?
    var userId : String?
    var userNickname : String?
    
    enum CodingKeys: String, CodingKey {
        case menuId = "menu_id"
        case menuName = "menu_name"
        case reviewContent = "review_content"
        case reviewId = "review_id"
        case userId = "user_id"
        case userNickname = "user_nickname"
    }
}

struct User: Codable {
    var myReviews : [DocumentReference] = []
    
    enum CodingKeys: String, CodingKey {
        case myReviews = "my_reviews"
    }
}

//MARK: - firebase 데이터 땡기기 관련
extension User {
    func fetchMyReviews(completion : @escaping ([DemoReview]?) -> Void) {
        // 병렬로 처리
        let group = DispatchGroup()
        
        var fetchedDemoReviews : [DemoReview] = []
        
        self.myReviews.forEach { ref in
            
            group.enter()
            //reference로 처리된 문서의 내용 가져오기
            ref.getDocument { snapShot, err in
                if err != nil {
                    group.leave()
                    return
                }
                guard let snapShot = snapShot else {
                    group.leave()
                    return
                }
            
                print(#fileID, #function, #line, "- demoReview checking⭐️: \(snapShot.data())")
                if let fetchedDemoReviewItem = try? snapShot.data(as: DemoReview.self) {
                    fetchedDemoReviews.append(fetchedDemoReviewItem)
                }
                
                group.leave()
            }
        }
        //작업이 완료됨(즉, reference로 처리된 문서를 다 가져옴)
        group.notify(queue: .main){
            print(#fileID, #function, #line, "- 완료")
            completion(fetchedDemoReviews) /fetchMyReviews()의 completion실행
        }
    }
}

//User정보를 가지고 오는 firebase함수 작성
final class FirebaseManager {
		//싱글톤으로 만들기
    static let shared = FirebaseManager()
    
    let userDbRef = Firestore.firestore().collection("User")
    
    //User정보를 가져오는 함수
    func fetchCurrentUser(userId: String, completion: @escaping (User?) -> Void) {
				//firebase에서 정보 요청
        userDbRef.document(userId).getDocument { snapShot, err in
                if err != nil {
                    completion(nil)
                    return
                }
                guard let snapShot = snapShot else {
                    completion(nil)
                    return
                }
                print(#fileID, #function, #line, "- user snapshot data check⭐️: \(snapShot.data())")
                let fetchedUser = try? snapShot.data(as: User.self)
								//User정보를 가져와서 파싱하고 completion을 터트림
                completion(fetchedUser)
            }
    }
    
}

//fetchCurrentUser함수 실행
FirebaseManager.shared.fetchCurrentUser(userId: "rayoung1", completion: { user in
				//user에 관한 정보를 다 가져왔을 때 
        if let user = user {
						//user type에 정의된 fetchMyReviews호출
            user.fetchMyReviews(completion: { myReviews in
                print(#fileID, #function, #line, "- myReviews: \(myReviews)")
            })
        }
    })

참고한 사이트

iOS Dispatch Group 사용해보기 · 지혜의 개발공부로그

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

[iOS - swift] Dispatch Group

profile
ios 개발을 하고있는 라무의 사적인 기술 블로그

0개의 댓글