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

相关推荐
2603_965896621 小时前
Grid 网格布局|CSS 二维页面排版体系
前端·css
有味道的男人1 小时前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript
lovingsoft2 小时前
Chrome Lighthouse 详解
前端·chrome
Qlly2 小时前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝2 小时前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦2 小时前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡3 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI3 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95273 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人3 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring