Swift串行上传多个图片

场景

在即时通讯模块中,用户可一次性选择多张图片进行发送。系统需根据用户选择的顺序,在界面上按序展示这些图片。在上传过程中,每张图片需先保存一份本地副本,再进行网络上传操作。

为了确保最终发送的消息顺序一致,系统以服务器端完成上传并发送消息的时间戳为准。为实现上传过程中消息顺序的一致性,图片上传需采用串行处理:当前图片消息上传完成并收到服务器回执(无论成功或失败)后,才进行下一张图片的上传与发送操作。

实现方案

为保证多图上传与消息发送过程中的顺序一致性和稳定性,采用以下串行上传机制:

  1. 图片选择与本地预处理

    • 用户可多选图片,系统按用户选择顺序记录图片列表。
    • 每张图片在上传前保存至本地缓存目录(避免中途丢失,支持失败重试)。
  2. 消息队列构建与串行上传调度

    • 构建一个串行上传任务队列,将每张图片包装成一个上传任务(包含本地路径、顺序索引、上传状态等)。
    • 启动上传调度器,每次仅处理队首任务。
  3. 单张图片上传与消息发送流程

    • 调用上传接口上传当前图片,上传成功后获取服务器文件 URL。
    • 构造消息体(携带图片 URL、顺序索引等),调用发送接口发送消息。
    • 成功或失败均记录状态,执行下一个上传任务。
  4. 顺序保障机制

    • 前一张图片的消息发送完成(含发送失败)后,才开始处理下一张图片。
    • 消息发送结果由服务端回执确认(携带时间戳),以此决定最终展示顺序。
    • 若服务器允许自定义排序字段,也可携带客户端顺序索引作为备用排序依据。
  5. 界面展示逻辑

    • 图片列表在界面按用户选择顺序实时展示上传进度。
    • 上传成功后替换为正式消息内容(包括时间戳、图片缩略图等)。
    • 若某张图片上传或发送失败,可在 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

    }

如果有更好的方案可提出

Demo地址

相关推荐
烛阴1 小时前
Python多进程开发实战:轻松突破GIL瓶颈
前端·python
爱分享的程序员1 小时前
前端面试专栏-主流框架:11. React Router路由原理与实践
前端·javascript·react.js·面试
weixin_459074351 小时前
在el-image组件的预览中添加打印功能(自定义功能)
前端·javascript·vue.js
知否技术1 小时前
2025微信小程序开发实战教程(三)
前端·微信小程序
海的诗篇_2 小时前
前端开发面试题总结-vue3框架篇(二)
前端·javascript·vue.js·面试·前端框架·vue
大熊程序猿2 小时前
quartz 表达式最近10次执行时间接口编写
java·服务器·前端
广药门徒2 小时前
ad24智能pdf输出的装配图没有四个边角那里的圆孔
前端·javascript·pdf
zhangxingchao2 小时前
Flutter与H5页面的交互
前端
粥里有勺糖2 小时前
视野修炼第124期 | 终端艺术字
前端·javascript·github
zhangxingchao3 小时前
Flutter常见Widget的使用
前端