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地址

相关推荐
trsoliu几秒前
2025年Web前端最新趋势:React基金会成立、AI编码助手崛起与Astro极速搜索
前端·javascript·react.js
一 乐3 分钟前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·商城推荐系统
亿元程序员9 分钟前
为什么游戏公司现在都喜欢用protobuf?
前端
鹏多多11 分钟前
React瀑布流Masonry-Layout插件全方位指南:从基础到进阶实践
前端·javascript·react.js
fruge18 分钟前
前端数据可视化实战:Chart.js vs ECharts 深度对比与实现指南
前端·javascript·信息可视化
卓码软件测评27 分钟前
借助大语言模型实现高效测试迁移:Airbnb的大规模实践
开发语言·前端·javascript·人工智能·语言模型·自然语言处理
IT_陈寒32 分钟前
SpringBoot 3.0实战:这套配置让我轻松扛住百万并发,性能提升300%
前端·人工智能·后端
♡喜欢做梦33 分钟前
Spring Web MVC 入门秘籍:从概念到实践的快速通道(上)
前端·spring·mvc
Dragon Wu40 分钟前
Taro 自定义tab栏和自定义导航栏
前端·javascript·小程序·typescript·前端框架·taro
艾小码1 小时前
2025年前端菜鸟自救指南:从零搭建专业开发环境
前端·javascript