问题诉求
我想要实现在macOS应用中,上传图片后进入压缩任务。

当我前面上传的图片还在压缩中,新的图片被上传。
如果调用压缩方法,压缩上传的图片,必定会导致重复压缩的场景,因此就需要实现一个经典的任务队列调度问题。

因此,就需要实现一个高效、有序、稳定的图片压缩流程,需要考虑的因素有:
1、压缩是异步耗时操作。
2、用户可能随时添加图片。
3、了解每张图片的状态(未压缩 / 压缩中 / 已完成 / 失败)。
队列模型
可以使用任务队列+状态管理解决这一问题:
1、为每张图片维护状态
为每张图片模型加一个枚举字段 compressionState压缩状态:
enum CompressionState {
case pending // 等待压缩
case compressing // 正在压缩
case completed // 已压缩完成
case failed // 压缩失败
}
可以根据图片的compressionState字段,了解图片当前的压缩状态。

2、设计一个图片压缩任务队列
使用一个串行队列(可选带优先级)来顺序处理压缩任务:
import SwiftUI
class CompressionManager:ObservableObject {
static let shared = CompressionManager()
// 任务队列:存储被压缩的图片
private var taskQueue: [CustomImages] = []
// 当前有无被压缩的图片,isCompressing表示当前有图片被压缩,其他图片需要等待
private var isCompressing = false
// 进入压缩队列,开始压缩
func enqueue(_ image: CustomImages) {
guard image.compressionState == .pending else { return } // 防止重复压缩
// 将图片添加到任务队列
taskQueue.append(image)
compressionTask()
}
// 压缩任务:
// 1、判断压缩和任务队列
// 2、修改当前压缩状态和图片的压缩状态
// 3、当压缩成功后,修改图片的压缩状态,移除任务队列中已经压缩图片,将当前压缩状态改为false,开始压缩下一个
private func compressionTask() {
// 如果当前没有被压缩的图片,获取任务队列的第一张图片,否则退出
guard !isCompressing, let task = taskQueue.first else { return }
// 设置压缩状态为 true
isCompressing = true
DispatchQueue.main.async {
// 修改当前图片为压缩中
task.compressionState = .compressing
}
compressImage(task) { success in
DispatchQueue.main.async {
task.compressionState = success ? .completed : .failed
self.taskQueue.removeFirst()
self.isCompressing = false
self.compressionTask() // 继续下一个
}
}
}
}
3、用户添加图片时只加入队列,不直接压缩
在 UI 上传图片时:
let newImage = ImageItem(url: fileURL, compressionState: .pending)
CompressionManager.shared.enqueue(newImage)
4、图片压缩方法
func compressImage(_ image: CustomImages, completion: @escaping (Bool) -> Void) {
// 如果压缩成功,返回 true
if success {
completion(true)
} else {
completion(false)
}
}
总结
实现图片压缩可以使用串行任务。
创建管理图片的单例模型,每次新增新的图片,都会追加到单例模型的图片数组中,并调用压缩图片的方法。
压缩图片的方法会判断当前有无被压缩的图片,如果有则return,防止重复压缩。
如果当前没有压缩的图片,就尝试获取单例模型图片数组的第一个对象,如果有该对话的话,然后标记当前的状态为在压缩,并更新图片对象的压缩状态。
当图片对象被压缩后,通过回调闭包,更新图片对象的压缩状态,移除图片数组中已经被压缩的第一个对象,开始压缩下一个对象。

通过压缩图片模型的isCompressing变量,实现真正的串行任务。
此外,使用任务队列时,因为需要修改很多参数,可能会遇到SwiftUI无法跟踪数组内元素属性变化,而不同步SwiftUI的问题,具体请见《SwiftUI无法追踪数组中对象的内部属性变化问题》。
相关文章
1、Swift基于实际案例的异步任务调度队列:https://fangjunyu.com/2024/12/08/swift%e5%9f%ba%e4%ba%8e%e5%ae%9e%e9%99%85%e6%a1%88%e4%be%8b%e7%9a%84%e5%bc%82%e6%ad%a5%e4%bb%bb%e5%8a%a1%e8%b0%83%e5%ba%a6%e9%98%9f%e5%88%97/
2、SwiftUI无法追踪数组中对象的内部属性变化问题:https://fangjunyu.com/2025/07/12/swiftui%e6%97%a0%e6%b3%95%e8%bf%bd%e8%b8%aa%e6%95%b0%e7%bb%84%e4%b8%ad%e5%af%b9%e8%b1%a1%e7%9a%84%e5%86%85%e9%83%a8%e5%b1%9e%e6%80%a7%e5%8f%98%e5%8c%96%e9%97%ae%e9%a2%98/