场景
在即时通讯模块中,用户可一次性选择多张图片进行发送。系统需根据用户选择的顺序,在界面上按序展示这些图片。在上传过程中,每张图片需先保存一份本地副本,再进行网络上传操作。
为了确保最终发送的消息顺序一致,系统以服务器端完成上传并发送消息的时间戳为准。为实现上传过程中消息顺序的一致性,图片上传需采用串行处理:当前图片消息上传完成并收到服务器回执(无论成功或失败)后,才进行下一张图片的上传与发送操作。
实现方案
为保证多图上传与消息发送过程中的顺序一致性和稳定性,采用以下串行上传机制:
-
图片选择与本地预处理
- 用户可多选图片,系统按用户选择顺序记录图片列表。
- 每张图片在上传前保存至本地缓存目录(避免中途丢失,支持失败重试)。
-
消息队列构建与串行上传调度
- 构建一个串行上传任务队列,将每张图片包装成一个上传任务(包含本地路径、顺序索引、上传状态等)。
- 启动上传调度器,每次仅处理队首任务。
-
单张图片上传与消息发送流程
- 调用上传接口上传当前图片,上传成功后获取服务器文件 URL。
- 构造消息体(携带图片 URL、顺序索引等),调用发送接口发送消息。
- 成功或失败均记录状态,执行下一个上传任务。
-
顺序保障机制
- 前一张图片的消息发送完成(含发送失败)后,才开始处理下一张图片。
- 消息发送结果由服务端回执确认(携带时间戳),以此决定最终展示顺序。
- 若服务器允许自定义排序字段,也可携带客户端顺序索引作为备用排序依据。
-
界面展示逻辑
- 图片列表在界面按用户选择顺序实时展示上传进度。
- 上传成功后替换为正式消息内容(包括时间戳、图片缩略图等)。
- 若某张图片上传或发送失败,可在 UI 上展示失败状态并提供"重试"入口。
实现代码
Swift
init(maxConcurrentOperationCount : Int = 1) {
queue = OperationQueue()
queue.maxConcurrentOperationCount = maxConcurrentOperationCount
}
func addTask(key: String,
file : T,
_ work: @escaping (UploadTask<T>) -> Void) {
let task = UploadTask(key: key, file: file, work: work)
task.completionBlock = { [weak self, weak task] in
guard let self, let task else { return }
self.lock.lock()
if self._currentTask === task {
self._currentTask = nil
}
self.lock.unlock()
self.checkAndStopMonitoring()
}
task.onDidStart = { [weak self] op in
guard let self = self else { return }
self.lock.lock()
self._currentTask = op
self.lock.unlock()
// 取消旧定时器()
self.stopMonitoring()
self.startMonitoringIfNeeded()
}
queue.addOperation(task)
}
创建任务
Swift
{
let key: String
private let work: (UploadTask<T>) -> Void
//开始回调
var onDidStart: ((UploadTask) -> Void)?
private var _executing = false
private var _finished = false
weak var operationDelegate : UploadTaskDelegate?
override var isAsynchronous: Bool { true }
override var isExecuting: Bool { _executing }
override var isFinished: Bool { _finished }
private var file: T?
init(key: String,
file : T,
work: @escaping (UploadTask<T>) -> Void) {
self.key = key
self.work = work
self.file = file
}
override func start() {
if isCancelled {
finish()
return
}
self.onDidStart?(self)
work(self)
self._executing = true
self.uploader()
}
func uploader() {
self.operationDelegate?.startOperation(with: self.file, operation: self)
}
func finish() {
debugPrint("任务结束 \(self.key)")
willChangeValue(for: \.isExecuting)
willChangeValue(for: \.isFinished)
_executing = false
_finished = true
didChangeValue(for: \.isExecuting)
didChangeValue(for: \.isFinished)
}
}
为了防止上传过程中某个任务卡住,会影响后续的任务执行,增加了监听机制,设置超过某个时间 Demo中设置的是30S
失效,按照具体业务设置不同的阈值,如果超过时间,取消上传,按失败处理或其他操作。
swift
private func startMonitoringIfNeeded() {
guard monitoringTimer == nil else { return }
guard let task = _currentTask else { return }
let key = task.key
self.lastKey = key
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now() + monitorInterval)
timer.setEventHandler { [weak self] in
guard let self = self else { return }
self.lock.lock()
defer { self.lock.unlock() }
if let current = self._currentTask, current.key == key, current.isExecuting {
// 自动取消任务 并上报
current.cancel()
self.stopMonitoring()
}
}
monitoringTimer = timer
timer.resume()
}
private func checkAndStopMonitoring() {
if queue.operations.isEmpty {
stopMonitoring()
}
}
private func stopMonitoring() {
monitoringTimer?.cancel()
monitoringTimer = nil
}

