DispatchGroup 是 GCD(Grand Central Dispatch) 提供的一个机制,用于跟踪一组异步任务的完成情况。
可以在多个任务开始时调用 .enter(),每个任务完成后调用 .leave(),最后用 .notify() 或 .wait() 来统一处理这些任务完成后的逻辑。
用途场景
等待多个异步任务完成之后,统一更新 UI;
下载多个文件,全部完成后打包;
并行执行多个任务,全部结束后提交结果。
组成结构
1、enter():表示将要开始一个任务,告诉 DispatchGroup:要开始工作了。
2、.leave():表示完成了一个任务,告诉 DispatchGroup:这项任务做完了。必须和 .enter() 配对使用,多一次 leave() 就崩溃,少一次也会卡住不触发 notify()。
3、.notify(queue:):当所有 enter/leave 都配对完毕,任务组结束时被调用,可以在这里更新 UI 等。
4、.wait():同步阻塞当前线程,直到所有任务完成。不建议在主线程使用(容易卡 UI)。
使用示例
1、加载三张图片后,再更新UI:
let group = DispatchGroup()
var images: [NSImage] = []
for i in 1...3 {
group.enter()
loadImageAsync(index: i) { image in
if let img = image {
images.append(img)
}
group.leave()
}
}
// 所有图片加载完毕后,通知主线程更新 UI
group.notify(queue: .main) {
print("全部加载完成,共 \(images.count) 张图")
}
在for-in循环中,调用了闭包。
如果不使用DispatchGroup,那么可能进入第一次循环后,闭包因为是异步的,所以就会输出“全部加载完成”的信息。

使用DispatchGroup后,会等待异步闭包完成后,再输出“全部加载完成”。

实际案例
在压缩图片的应用中,我使用onDrop方法将图片拖入应用中。
当我拖入图片后,发现应用没有显示任何图片。

onDrop代码为:
.onDrop(of: [.image], isTargeted: $isHovering) { providers in
var imageURLs: [URL] = []
for provider in providers {
if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
provider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
guard let fileURL = url, let imageURL = saveURLToTempFile(fileURL: fileURL) else { return }
imageURLs.append(imageURL) // 将图片添加到队列
}
}
}
savePictures(url: imageURLs) // 调用存储图片的方法
}
这里的流程是,使用for-in方法遍历所有拖入图片,将所有图片的URL添加到imageURLs数组中,最后调用存储图片的方法。
但代码实际上并未按照我想要的流程实现。
我尝试添加输出语句,检查imageURLs数组:
print("当前保存的URL数量:\(imageURLs.count)")
savePictures(url: imageURLs)
print("imageURL:\(imageURL),添加到队列")
发现输出的内容为:
当前保存的URL数量:0
imageURL:file:///Users/fangjunyu/Library/Containers/com.fangjunyu.ImageSlim/Data/tmp/eso1907a_%E5%89%AF%E6%9C%AC2.tif,添加到队列
这表示还未进入for-in循环,就先调用的savePictures方法。
这实际上是Swift异步的经典陷阱:因为 loadFileRepresentation 是一个异步方法,它不会阻塞 for 循环的执行。
for provider in providers {
provider.loadFileRepresentation(...) { url, error in
// 这个是异步回调,会稍后执行
imageURLs.append(imageURL)
}
}
savePictures(url: imageURLs) // 这行在闭包还没执行时就已经调用了
这也就意味着异步方法loadFileRepresentation会立即返回,闭包稍后执行。
savePictures(url: imageURLs) 这句会立即在 for 循环结束后调用;
这时imageURLs 还是空的,因为闭包尚未执行,所以应用不会添加任何图片。
解决方案就是使用DispatchGroup,当所有图片都处理完后,统一压缩/添加。
.onDrop(of: [.image], isTargeted: $isHovering) { providers in
var imageURLs: [URL] = []
let group = DispatchGroup() // 创建 DispatchGroup
for provider in providers {
if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
group.enter() // 开始任务
provider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
defer { group.leave() } // 结束任务
guard let fileURL = url, let imageURL = saveURLToTempFile(fileURL: fileURL) else { return }
imageURLs.append(imageURL) // 将图片添加到队列
}
}
}
group.notify(queue: .main) { // 所有图片添加完成后,再调用压缩图片方法
savePictures(url: imageURLs)
}
}
通过group.enter()和group.leave(),在使用异步方法时,会添加异步任务。
当for-in的异步方法全部调用完成后,再调用group.notify(queue: .main)方法,统一压缩图片。
最终实现添加图片的真实场景。

总结
DispatchGroup适用于异步任务,在异步任务的场景下,如果不使用DispatchGroup,就会导致应用处理异步任务时,应用会继续调用其他的代码,这样就会破坏想要实现的任务顺序。
通过DispatchGroup可以实现在全部异步任务完成后,调用相关的完成代码。
enter()和leave()必须成对使用,否则会导致崩溃或卡住。
不要再主线程使用 wait()方法,否则会卡住UI。
相关文章
1、Swift管理多线程任务框架GCD:https://fangjunyu.com/2024/11/02/swift%e7%ae%a1%e7%90%86%e5%a4%9a%e7%ba%bf%e7%a8%8b%e4%bb%bb%e5%8a%a1%e6%a1%86%e6%9e%b6gcd/
2、Swift中的线程模型:https://fangjunyu.com/2024/12/25/swift%e4%b8%ad%e7%9a%84%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b/