DispatchGroup 與 DispatchWorkItem
Published in
8 min readAug 9, 2023
GCD(Grand Central Dispatch)裡面的兩種方法介紹:
- DispatchGroup:
DispatchGroup
是一種用於追蹤一組非同步任務(通常是使用 GCD 創建的)的進度的機制。它可以幫助你確定所有的任務是否已經完成,從而在所有任務都完成後執行特定的程式碼。你可以使用enter()
方法來追蹤進入群組的任務數量,使用leave()
方法來標記任務已完成,然後可以使用notify(queue:execute:)
方法在群組中的所有任務都完成後執行指定的程式碼。 - DispatchWorkItem:
DispatchWorkItem
是一個輕量級的任務(closure),可以使用 GCD 運行它,也可以在其他地方運行。它不僅允許你定義你的任務,還可以控制任務的取消、延遲執行等。
DispatchGroup
用於管理一組非同步任務的進度,而 DispatchWorkItem
則用於創建和控制單個任務。這兩個類別結合使用可以有效地處理多個非同步任務,並在它們完成時執行相應的程式碼。
使用DispatchGroup
一次請求多次API,要特別注意的是 enter 的次數要跟 leave 的次數是相等的,
func randomBreedImages(completion: @escaping ([[String: URL]]) -> Void) {
var breeds: [[String: URL]] = []
//創建 DispatchGroup 同步多個非同步任務
let dispatchGroup = DispatchGroup()
//進入一個任務
dispatchGroup.enter()
dispatchGroup.enter()
Network.getBreedList { breedList in
if let breed = breedList.randomElement() {
do {
try Network.getImageUrl(breed: breed) { url in
if let url {
breeds.append([breed : url])
print("breeds1:\(breeds)")
//加入完畢就直接離開任務
dispatchGroup.leave()
}
}
} catch {
print("Error fetching image for breed \(breed): \(error)")
//如果網路請求失敗就進去 catch 一樣直接離開任務
dispatchGroup.leave()
}
Network.getSubBreedList(breed) { subList in
for subBreed in subList {
let combinedBreed = breed + " " + subBreed
//這邊只要重複就不在請求網路
if !breeds.contains { $0.keys.first == combinedBreed } {
//在 for 裡面創建多個任務進入點
dispatchGroup.enter()
do {
try Network.getImageUrl(breed: breed, subBreed: subBreed) { url in
if let url {
breeds.append([breed + " " + subBreed : url ])
print("breeds2:\(breeds)")
//每請求完畢就離開任務
dispatchGroup.leave()
}
}
} catch {
print("Error fetching image for breed \(breed) and subBreed \(subBreed): \(error)")
dispatchGroup.leave()
}
}
}
}
}
}
dispatchGroup.leave()
dispatchGroup.wait()
//等待都請求完畢後在收集全部的任務後執行
dispatchGroup.notify(queue: .main) {
completion(breeds)
print("main \(breeds.count)")
}
}
這邊使用DispatchWorkItem
來達到使用搜尋匡每輸入一個就自動向 API 請求一次,$0.lowercased()
是將品種名稱轉換成小寫字串,以便進行比對,請求完成的會 reloadData 讓畫面更新,為了防止大量請求而使用了 searchWorkItem?.cancel 與 [weak self] 都與處理多線程編程(concurrent programming)和避免引起強引用循環(retain cycle)有關。
var searchWorkItem: DispatchWorkItem?
let dispatchQueue = DispatchQueue(label: "searchQueue", qos: .userInitiated)
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
var searchContent: [[String: URL]] = []
searchWorkItem?.cancel()
if searchText.isEmpty {
//進入 seachBar 後 讓一起同步存取值的變數,賦予給主要的變數 breedContent
DispatchQueue.main.async {
self.breedContent = self.content
self.collectionView.reloadData()
}
}else{
searchWorkItem = DispatchWorkItem { [weak self] in
Network.getBreedList { breedList in
//利用 searchText 來過濾 寵物的列表
let filtered = breedList.filter { $0.lowercased().contains(searchText.lowercased()) }
for breedName in filtered {
// 取得到過濾後的列表向網路請求圖片
Network.getImageUrl(breed: breedName) { url in
if let url {
// 利用一個專門存取 search 結果的變數
searchContent.append([breedName : url])
DispatchQueue.main.async {
self?.breedContent = []
self?.breedContent = searchContent
self?.collectionView.reloadData()
}
}
}
}
}
}
}
dispatchQueue.asyncAfter(deadline: .now() + 0.5, execute: searchWorkItem!)
}
GPT補充:
DispatchWorkItem.cancel
:DispatchWorkItem
是 GCD(Grand Central Dispatch)中的一個類別,用於表示要在某個佇列上執行的工作(task)。當你創建一個DispatchWorkItem
並將其提交到一個佇列上時,你可以稍後使用cancel
方法來取消這個工作的執行。這在某些情況下可能很有用,例如,如果你在主線程上執行了一個耗時的工作,但在某個點上你希望取消它,你可以調用cancel
方法。這可以幫助你更好地控制和管理佇列中的工作。[weak self]
: 在 Swift 中,當你在一個 closure 內部捕獲一個對self
的引用時,可能會導致強引用循環。這種情況在當一個對象(通常是類實例)持有一個 closure,而 closure 同時又捕獲了這個對象的引用時發生。為了避免這種強引用循環,常見的方法是在 closure 內部使用[weak self]
或[unowned self]
來捕獲對self
的弱引用或無主引用。[weak self]
的主要目的是避免強引用循環。當你在 closure 內部使用[weak self]
時,即使在 closure 中持有了對self
的引用,它也不會增加self
的引用計數。如果在後續的執行過程中,self
已經被釋放,則使用弱引用[weak self]
可以確保不會對已釋放的對象進行操作。
總結起來,DispatchWorkItem.cancel
用於取消工作項目的執行,而 [weak self]
用於避免在 closure 中引起強引用循環,從而避免內存泄漏。在處理多線程編程和保護對象生命週期方面,這兩者都是有用的工具。