iOS实际开发中使用Alamofire实现多文件上传(以个人相册为例)

news2025/1/11 12:40:52

引言

在移动应用中,图片上传是一个常见的功能,尤其是在个人中心或社交平台场景中,用户经常需要上传图片到服务器,用以展示个人风采或记录美好瞬间。然而,实现多图片上传的过程中,如何设计高效的上传逻辑并结合用户体验显得尤为重要。

本篇博客将通过一个具体实例——个人中心的相册功能,来介绍如何使用 Alamofire 实现多文件上传。我们将从图片选择开始,逐步实现图片数据的封装、上传请求的构建以及上传进度的展示,最终完成一套完整的多图片上传解决方案。如果你也正在寻找一种高效的实现方法,希望本文能为你提供一些思路与启发。

功能需求分析

用户的个人信息页面会展示用户的一些精选照片墙,为了补充这个信息在用户编辑页添加了的上传相册图片的功能,上传到相册的图片最多为6张,可以多选,但是已选择和已上传的总数不能超过6。

关于相册选择我们可以使用系统为我们提供的PHPickerViewController来实现,使用起来十分方便,可以直接设置选择资源类型和最大个数,关于它的使用在之前的博客中也有过介绍,有兴趣的同学可以看下面这篇博客。

iOS 系统提供的媒体资源选择器(PHPickerViewController)-CSDN博客文章浏览阅读1.6k次,点赞10次,收藏20次。在前面的博客中我们已经介绍了一个系统为我们提供的媒体选择器UIImagePickerController,它的功能很强大,但是唯一的不足就是只能选取单个媒体资源,而PHPickerViewController恰恰弥补了这一空缺。PHPickerViewController是iOS 14及更高版本中引入的一个现代化媒体选择器,旨在替代UIImagePickerController。它不仅提供了更灵活的媒体选择功能,还拥有更现代的用户界面。_phpickerviewcontrollerhttps://blog.csdn.net/weixin_39339407/article/details/140918416选择了相册的图片之后,下一步就上传图片资源到相册。Alamofire为我们提供了完整的上传方法但是由于我们需要上传多个资源,所以仍然有一些数据来需要我们处理。

接口设计

在讨论如何实现多文件上传之前,我们首先需要了解一下后端的接口设计,每个服务端对于文件上传的方式都并不是一成不变的。

在上传相册图片的需求中,整个过程被分成了两个接口:

  1. 文件上传接口:负责文件上传,需要设置文件的类型,以及自定义参数,来决定文件上传是用来做什么的,每次只能上传一个文件。会返回上传后的资源地址。
  2. 相册更新接口:使用上传后的资源地址构建列表,来更新相册。

代码实现

那我们跳过关于UI的布局刷新以及照片的选择功能,将所有精力集中到文件上传,照片选择完成之后会传递回来一个等待上传的图片数组,为了更符合业务场景,我们需要根据图片数组来构建一组新的相册模型,并添加到相册列表,然后执行上传操作。

选择照片后的处理:

    /// 选择相册图片
    /// - Parameter images: 图片数组
    func uploadAlbum(images: [UIImage]) {
        //1. 先刷新
        for image in images {
            let model = MWEditPhotoModel()
            model.image = image
            model.uploadStatus = .none
            editUserPhotos.append(model)
        }
        self.tableView.reloadData()
        //2.开始执行上传操作
        self.presenter.requestUploadAlbumData(editPhotoModels: editUserPhotos) {[weak self] in
            guard let self = self else { return }
            self.tableView.reloadData()
        }
    }
  1. 根据选择上传的图片数组构建新的相册模型,并添加到相册列表中,刷新列表。
  2. 将整个相册列表当做参数传递到上传方法中。

首先会根据传递进来的相册列表数据进行过滤,获取到需要上传的进行上传:

    /// 信号量最大并发控制为2
    private let semaphore = DispatchSemaphore(value: 2)
  /// 上传用户相册
    func requestUploadAlbumData(editPhotoModels:[MWEditPhotoModel], completion: (() -> Void)?) {
        uploadAlbumCompletion = completion
        // 获取需要上传的模型
        let needUploadModels = editPhotoModels.filter { $0.uploadStatus == .none && $0.isAdd == false }
        // 上传图片
        var needUploadCount = needUploadModels.count
        let semaphore = self.semaphore
        globalQueue.async {[weak self] in
            guard let self = self else {
                return
            }
            for model in needUploadModels {
                model.uploadStatus = .uploading
                semaphore.wait()
                MWLogHelper.debug("开始上传图片",context: "MWEditProfilePresenter")
                self.uploadAlbumImages(model) {[weak self] success in
                    MWLogHelper.debug("上传图片结果\(success)",context: "MWEditProfilePresenter")
                    defer {
                        // 无论结果如何,确保释放信号量
                        semaphore.signal()
                    }
                    guard let self = self else {
                        return
                    }
                    if success == false {
                        model.uploadStatus = .fail
                    }
                    needUploadCount -= 1
                    if needUploadCount == 0 {
                        self.requestUpdateAlbumData(editPhotoModels: editPhotoModels)
                    }
                }
            }
        }
    }
  1. 首先创建了一个信号量来控制可以同时上传的最大个数。
  2. 过滤出需要上传的数据执行上传操作。
  3. 当所有资源上传完成之后在根据列表数据来更新相册。

那我们先来看一下上传方法:

    // 上传相册图片
    private func uploadAlbumImages(_ editPhotoModel: MWEditPhotoModel,complection:((Bool)->Void)? = nil) {
        guard let imageData = editPhotoModel.image?.compressImageQuality(maxSize: Int(640 * 640)) else {
            complection?(false)
            return
        }
        MWNetworkHelper.upLoadFile(data: imageData, kinds: "photo", fileType: MWUploadFileType.image, fileName: "photo.jpg") { result in
            if let imageUrl = result?["photo"] {
                var albumModel =  MWProfileAlbumModel()
                albumModel.littleUrl = imageUrl
                albumModel.srcUrl = imageUrl
                editPhotoModel.albumModel = albumModel
                editPhotoModel.uploadStatus = .success
                complection?(true)
            } else {
                editPhotoModel.uploadStatus = .fail
                complection?(false)
            }
        } notRedirectErrorCallback: {
            complection?(false)
            MWToast.showToast(MWLocaleStringHelper.getString("Upload Failed"))
        }
    }


    /// 上传文件
    /// - Parameters:
    ///  - data: 文件数据
    ///  - kinds: 参数
    ///  cover 封面, facebookSharedImage 分享, portrait 头像, pic  图片, zip  压缩包, video  视频,   , ,photo 相册, photoProcess  相册模糊, liveCover 直播间封面 photoWall, 个人主页, photoWallBig 个人主页大图
    ///  - fileType: 文件类型
    ///  - fileName: 文件名称
    ///  - completion: 上传结果
    public class func upLoadFile(data: Data, kinds: String, fileType: MWUploadFileType, fileName: String, completion: @escaping (([String: String]?) -> Void), notRedirectErrorCallback: @escaping () -> Void) {
        
        // 上传地址
        var url = "\(MWAPIHost.host)/rest/api/usergate/uploadFile"
        // 上传header
        var headers = [String:String]()
        // 重定向
        let redirectHandler = MWRedirectHandler(data: data, kinds: kinds, fileType: fileType, fileName: fileName, completion: completion)
        AF.upload(multipartFormData: { multipartFormData in
            //图片
            var mimeType = "image/jpg"
            if fileType == .video {
                mimeType = "video/mp4"
            }
            multipartFormData.append(data, withName: "file",fileName: fileName, mimeType: mimeType)
            // 上传 kinds 参数
            if let kindsData = kinds.data(using: .utf8) {
                multipartFormData.append(kindsData, withName: "kinds")
            }
        }, to: url,method: .post, headers: MWAPIEncryEndpoint.api_postDynamic.headers).redirect(using: redirectHandler).responseJSON { response in
            if response.response?.statusCode != 307 {
                notRedirectErrorCallback()
            }
        }
    }
  1. 文件名称和文件类型需要根据文件的具体内容来设置。
  2. kinds为服务端自定的参数。
  3. 关于重定向的暂且可以不考虑,我们的文件上传接口进行了接口的重定向,一般来讲是不需要单独处理的。
  4. 文件上传成功之后根据返回结果,构建了新的相册数据,并同步到相册列表。

最后我们只需要等待所有文件上传完成之后,将所有数据同步到相册接口即可:

    /// 更新用户相册
    /// - Parameters:
    ///  - editPhotoModels: 编辑相册模型
    ///  - completion: 完成回调
    func requestUpdateAlbumData(editPhotoModels:[MWEditPhotoModel]) {
        let needUpdateModels = editPhotoModels.filter { $0.uploadStatus == .success || $0.uploadStatus == .noNeed }
        let albumList = needUpdateModels.map { $0.albumModel }
        var photoList = [[String: Any]]()
        for albumModel in albumList {
            var dict = [String: Any]()
            dict["littleUrl"] = albumModel?.littleUrl
            dict["srcUrl"] = albumModel?.srcUrl
            photoList.append(dict)
        }
        let params: [String: Any] = ["photoList": photoList]
        MWNetworkHelper.request(endpoint: MWAPIEncryEndpoint.api_updateUserPhotos, parameters: params) {[weak self] (model, data, error) in
            guard let self = self else { return }
            if error != nil {
                for model in needUpdateModels {
                    if model.uploadStatus == .success {
                        model.uploadStatus = .fail
                    }
                }
            }
            self.uploadAlbumCompletion?()
        }
    }

结语

本篇博客主要介绍了使用Alamofire实现多文件的上传功能,关于文件上传的具体方案还是需要根据服务端的接口设计来实施。本文以同步用户相册为例,将上传相册图片分为两个部分,上传和同步,当所有资源上传完成之后,执行相册的同步操作。并使用信号量来控制上传的最大并发数。

希望本篇博客能够在文件上传中给大家一些启发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2274902.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于phpstudy快速搭建本地php环境(Windows)

好好生活,别睡太晚,别爱太满,别想太多。 2025.1.07 声明 仅作为个人学习使用,仅供参考 对于CTF-Web手而言,本地PHP环境必不可少,但对于新手来说从下载PHP安装包到配置PHP环境是个非常繁琐的事情&#xff0…

ffmpeg 编译遇到的坑

makeinfo: error parsing ./doc/t2h.pm: Undefined subroutine &Texinfo::Config::set_from_init_file called at ./doc/t2h.pm line 24. 编译选项添加: --disable-htmlpages

Git:merge合并、冲突解决、强行回退的终极解决方案

首先还是得避免冲突的发生,无法避免时再去解决冲突,避免冲突方法: 时常做pull、fatch操作,不要让自己本地仓库落后太多版本;在分支操作,如切换分支、合并分支、拉取分支前,及时清理Change&#…

国内外网络安全政策动态(2024年12月)

▶︎ 1.2项网络安全国家标准获批发布 2024年12月6日,根据2024年11月28日国家市场监督管理总局、国家标准化管理委员会发布的中华人民共和国国家标准公告(2024年第29号),全国网络安全标准化技术委员会归口的2项网络安全国家标准正…

新兴的开源 AI Agent 智能体全景技术栈

新兴的开源 AI Agent 智能体全景技术栈 LLMs:开源大模型嵌入模型:开源嵌入模型模型的访问和部署:Ollama数据存储和检索:PostgreSQL, pgvector 和 pgai后端:FastAPI前端:NextJS缺失的一环:评估和…

通过一个含多个包且引用外部jar包的项目实例感受Maven的便利性

目录 1 引言2 手工构建3 基于Maven的构建4 总结 1 引言 最近在阅读一本Java Web的书籍1时,手工实现书上的一个含多个Packages的例子,手工进行编译、运行,最终实现了效果。但感觉到整个构建过程非常繁琐,不仅要手写各个源文件的编…

信息科技伦理与道德3:智能决策

1 概述 1.1 发展历史 1950s-1980s:人工智能的诞生与早期发展热潮 1950年:图灵发表了一篇划时代的论文,并提出了著名的“图灵测试”;1956年:达特茅斯会议首次提出“人工智能”概念;1956年-20世纪70年代&a…

Sql 创建用户

Sql server 创建用户 Sql server 创建用户SQL MI 创建用户修改其他用户密码 Sql server 创建用户 在对应的数据库执行,该用户得到该库的所有权限 test.database.chinacloudapi.cn DB–01 DB–02 创建服务器登录用户 CREATE LOGIN test WITH PASSWORD zDgXI7rsafkak…

【再谈设计模式】观察者模式~对象间依赖关系的信使

一、引言 在软件工程、软件开发的世界里,设计模式如同建筑蓝图中的经典结构,帮助开发者构建更加灵活、可维护和可扩展的软件系统。观察者模式就是其中一种极为重要的行为型设计模式,它在处理对象间的一对多关系时展现出独特的魅力。 二、定义…

如何设计一个注册中心?以Zookeeper为例

这是小卷对分布式系统架构学习的第8篇文章,在写第2篇文章已经讲过服务发现了,现在就从组件工作原理入手,讲讲注册中心 以下是面试题: 某团面试官:你来说说怎么设计一个注册中心? 我:注册中心嘛&…

【Unity3D】导出Android项目以及Java混淆

Android Studio 下载文件归档 | Android Developers Android--混淆配置(比较详细的混淆规则)_android 混淆规则-CSDN博客 Unity版本:2019.4.0f1 Gradle版本:5.6.4(或5.1.1) Gradle Plugin版本&#xff…

2024 China Collegiate Programming Contest (CCPC) Zhengzhou Onsite 基础题题解

今天先发布基础题的题解,明天再发布铜牌题和银牌题的题解 L. Z-order Curve 思路:这题目说了,上面那一行,只有在偶数位才有可能存在1,那么一定存在这样的数,0 ,1,100, 10000,那么反之,我们的数…

【FlutterDart】tolyui_feedback组件例子效果(23 /100)

上效果图 有12种位置展示效果;很能满足大部分需要 代码如下: import package:flutter/material.dart; import package:tolyui_feedback/tolyui_feedback.dart;class TolyTooltipDemo extends StatelessWidget {const TolyTooltipDemo({super.key});ove…

服务器攻击方式有哪几种?

随着互联网的快速发展,网络攻击事件频发,已泛滥成互联网行业的重病,受到了各个行业的关注与重视,因为它对网络安全乃至国家安全都形成了严重的威胁。面对复杂多样的网络攻击,想要有效防御就必须了解网络攻击的相关内容…

Mermaid 使用教程之流程图 - 从入门到精通

本文由 Mermaid中文文档 整理而来,并且它同时提供了一个Mermaid在线编辑器。 Mermaid 流程图 - 基本语法​ 流程图由节点(几何形状)和边(箭头或线)组成。Mermaid代码定义了如何创建节点和边,并适应不同的…

Flink系统知识讲解之:如何识别反压的源头

Flink系统知识之:如何识别反压的源头 什么是反压 Ufuk Celebi 在一篇古老但仍然准确的文章中对此做了很好的解释。如果您不熟悉这个概念,强烈推荐您阅读这篇文章。如果想更深入、更低层次地了解该主题以及 Flink 网络协议栈的工作原理,这里有…

网络-ping包分析

-a:使 ping 在收到响应时发出声音(适用于某些操作系统)。-b:允许向广播地址发送 ping。-c count:指定发送的 ping 请求的数量。例如,ping -c 5 google.com 只发送 5 个请求。-i interval:指定两…

国产linux系统(银河麒麟,统信uos)使用 PageOffice 实现后台生成单个PDF文档

PageOffice 国产版 :支持信创系统,支持银河麒麟V10和统信UOS,支持X86(intel、兆芯、海光等)、ARM(飞腾、鲲鹏、麒麟等)、龙芯(LoogArch)芯片架构。 PageOffice 版本&…

Win10微调大语言模型ChatGLM2-6B

在《Win10本地部署大语言模型ChatGLM2-6B-CSDN博客》基础上进行,官方文档在这里,参考了这篇文章 首先确保ChatGLM2-6B下的有ptuning AdvertiseGen下载地址1,地址2,文件中数据留几行 模型文件下载地址 (注意&#xff1…

Windows11环境下设置MySQL8字符集utf8mb4_unicode_ci

1.关闭MySQL8的服务CTRLshiftESC,找到MySQL关闭服务即可 2.找到配置文件路径(msi版本默认) C:\ProgramData\MySQL\MySQL Server 8.0 3.使用管理员权限编辑my.ini文件并保存 # Other default tuning values # MySQL Server Instance Config…