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

引言

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

本篇博客将通过一个具体实例——个人中心的相册功能,来介绍如何使用 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 = imagemodel.uploadStatus = .noneeditUserPhotos.append(model)}self.tableView.reloadData()//2.开始执行上传操作self.presenter.requestUploadAlbumData(editPhotoModels: editUserPhotos) {[weak self] inguard let self = self else { return }self.tableView.reloadData()}}
  1. 根据选择上传的图片数组构建新的相册模型,并添加到相册列表中,刷新列表。
  2. 将整个相册列表当做参数传递到上传方法中。

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

    /// 信号量最大并发控制为2private 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.countlet semaphore = self.semaphoreglobalQueue.async {[weak self] inguard let self = self else {return}for model in needUploadModels {model.uploadStatus = .uploadingsemaphore.wait()MWLogHelper.debug("开始上传图片",context: "MWEditProfilePresenter")self.uploadAlbumImages(model) {[weak self] success inMWLogHelper.debug("上传图片结果\(success)",context: "MWEditProfilePresenter")defer {// 无论结果如何,确保释放信号量semaphore.signal()}guard let self = self else {return}if success == false {model.uploadStatus = .fail}needUploadCount -= 1if 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 inif let imageUrl = result?["photo"] {var albumModel =  MWProfileAlbumModel()albumModel.littleUrl = imageUrlalbumModel.srcUrl = imageUrleditPhotoModel.albumModel = albumModeleditPhotoModel.uploadStatus = .successcomplection?(true)} else {editPhotoModel.uploadStatus = .failcomplection?(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"// 上传headervar 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 inif 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?.littleUrldict["srcUrl"] = albumModel?.srcUrlphotoList.append(dict)}let params: [String: Any] = ["photoList": photoList]MWNetworkHelper.request(endpoint: MWAPIEncryEndpoint.api_updateUserPhotos, parameters: params) {[weak self] (model, data, error) inguard let self = self else { return }if error != nil {for model in needUpdateModels {if model.uploadStatus == .success {model.uploadStatus = .fail}}}self.uploadAlbumCompletion?()}}

结语

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

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

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

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

相关文章

Cannot run program “docker“: CreateProcess error=2,系统找不到指定的文件

今天被这个问题坑了, 网上教程全是直接装插件就行 ,结果我连接可以成功 但是执行docker compose 就会出错, 检测配置 报错com.intellil,execution,process.ProcessNotCreatedException: Cannot run program “docker”: CreateProcess error2,系统找不到指定的文件 gpt 要我去…

Blender 2D动画与MATLAB数学建模:跨界融合的创新实践探索

文章目录 一、数据交换:从科学计算到创意表达的桥梁二、脚本自动化:从手动操作到无缝连接的升级三、跨界融合的创新实践意义《Blender 2D动画制作从入门到精通》亮点内容简介作者简介 《MATLAB数学建模从入门到精通》亮点内容简介作者简介 在数字创意与科…

HarmonyOS Next 日志工具介绍

HarmonyOS Next 日志工具介绍 在HarmonyOS Next开发中,日志是我们调试定位问题的主要手段,不管是hilog还是console,最终都可以输出到DevEco Studio的日志模块中: 在这里可以过滤应用进程、日志级别、日志内容呢,也可…

NUTTX移植到STM32

STM32移植NUTTX 1. Ubuntu下搭建开发环境1.1 先决条件1.2 下载 NuttX1.3 使用Make 进行编译1.4 烧录运行 2.通过NUTTX点亮LED2.1 部署操作系统2.2 修改配置文件2.3 编译运行程序 开发板:DshanMCUF407 官方开发文档:安装 — NuttX latest 文档 参考文档&…

【微服务】4、服务保护

微服务架构与组件介绍 单体架构拆分:黑马商城早期为单体架构,后拆分为微服务架构。跨服务调用与组件使用 服务拆分后存在跨服务远程调用,如下单需查询商品信息,使用openfeign组件解决。服务间调用关系复杂,需维护服务…

【网络安全 | 漏洞挖掘】通过模拟功能实现提权(Bugcrowd)

未经许可,不得转载。 我将与大家分享我在 Bugcrowd 的某个项目中发现的一个漏洞,该项目中有一个“用户模拟”功能。 什么是用户模拟? 用户模拟允许管理员在不知晓用户凭据的情况下“以用户身份登录”。这种功能常见于管理员需要调试问题、审查用户权限或解决投诉的平台中。…

安装MySQL的五种方法(Linux系统和Windows系统)

一.在Linux系统中安装MySQL 第一种方法:在线YUM仓库 首先打开MySQL官网首页 www.mysql.com 找到【DOWNLOADS】选项,点击 下拉,找到 【MySQL Community(GPL) Downloads】 在社区版下载页面中,【 MySQL Yum Repository 】链接为在线仓库安装…

【Spring Boot 应用开发】-01 初识

特性 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是帮助开发者快速创建独立的、生产级的基于 Spring 框架的应用程序。以下是 Spring Boot 的一些主要特点: 简化配置:Spring Boot 提供了自动配置机制,能够根据类路径…

AR 眼镜之-拍照/录像动效切换-实现方案

目录 📂 前言 AR 眼镜系统版本 拍照/录像动效切换 1. 🔱 技术方案 1.1 技术方案概述 1.2 实现方案 1)第一阶段动效 2)第二阶段动效 2. 💠 默认代码配置 2.1 XML 初始布局 2.2 监听滑动对 View 改变 3. ⚛️…

django解决跨域问题

# 1.安装django-cors-headers 库 pip install django-cors-headers -i https://pypi.tuna.tsinghua.edu.cn/simple2.添加到应用程序中 添加 corsheaders 到你的 INSTALLED_APPS 设置中: INSTALLED_APPS [...corsheaders,... ]3.添加中间件 MIDDLEWARE [...cor…

Ubuntu Bash工具

Ubuntu Bash工具 &#x1f4bb;⚡ Ubuntu Bash 工具&#x1f528; 指令列表1. &#x1f50b; 查看电池信息 (-b)2. &#x1f4bb; 查看 CPU 和内存使用情况 (-m)3. &#x1f504; 旋转屏幕 (-r)4. &#x1f513; 解锁屏幕 (-s)5. &#x1f310; 设置代理 (-p <proxy_url>…

【GoLang】两个字符串如何比较大小?以及字典顺序的比较规则

在 Go 语言中&#xff0c;字符串的比较是基于字典顺序进行的。 字典顺序的比较规则&#xff1a; 比较两个字符串从左到右逐个字符的Unicode码点值&#xff0c; 若比较结果不相等则将此结果作为字符串大小的结果&#xff0c; 若比较结果相等则比较下一位&#xff0c; 若其中一个…

《机器学习》——贝叶斯算法

贝叶斯简介 贝叶斯公式&#xff0c;又称贝叶斯定理、贝叶斯法则&#xff0c;最初是用来描述两个事件的条件概率间的关系的公式&#xff0c;后来被人们发现具有很深刻的实际意义和应用价值。该公式的实际内涵是&#xff0c;支持某项属性的事件发生得愈多&#xff0c;则该属性成…

matlab离线安装硬件支持包

MATLAB 硬件支持包离线安装 本文章提供matlab硬件支持包离线安装教程&#xff0c;因为我的matlab安装的某种原因&#xff08;破解&#xff09;&#xff0c;不支持硬件支持包的安装&#xff0c;相信也有很多相同情况的朋友&#xff0c;所以记录一下我是如何离线安装的&#xff…

Infineon PSoC 4 CapSense ModusToolbox IDE - 系统生态篇

本文档说明了 ModusToolbox 软体环境的 4 个层面&#xff0c;该环境为 CapSense 设备和生态系统提供支援。本文是 Infineon PSoC 4 CapSense ModusToolbox IDE-系统介绍的延伸篇 (Infineon PSoC 4 CapSense ModusToolbox IDE -系统介绍篇 - 大大通(简体站))。 什么是ModusToolb…

使用PVE快速创建虚拟机集群并搭建docker环境

安装Linux系统 这里以安装龙蜥操作系统AnolisOS8.9为例加以说明。 通过PVE后台上传操作系统ISO镜像。 然后在PVE上【创建虚拟机】&#xff0c;选定上传的龙蜥操作系统镜像进行系统安装。 注意&#xff1a;在安装过程中&#xff0c;要设定语言、时区、超管用户root的密码、普…

ElasticSearch内存占用率过高怎么办?

文章目录 1&#xff0c;先用top看看各个进程的内存占用情况2&#xff0c;不能简单的杀死进程&#xff0c;然后再重启。3&#xff0c;查看一下ElasticSearch进程的具体启动情况4&#xff0c;修改Elasticsearch 的Java堆内存 1&#xff0c;先用top看看各个进程的内存占用情况 先…

OpenHarmony AVScreenCaptureRecorder录屏开发指导

一、简介 OpenHarmony 5.0新增了AVScreenCaptureRecorder ArkTs API。用户可以调用录屏AVScreenCaptureRecorder API录制屏幕&#xff0c;采集音频源数据&#xff0c;获取封装后的音视频文件&#xff0c;然后通过文件的形式流转到其他模块进行播放或处理&#xff0c;用于以文件…

【Spring】构造方法注入 属性加final

在Spring框架中&#xff0c;构造方法注入是一种常见的依赖注入方式。通过构造方法注入&#xff0c;Spring容器会在创建Bean时自动调用相应的构造方法&#xff0c;并将所需的依赖作为参数传入。关于构造方法注入时属性是否加final关键字&#xff0c;主要有以下几点区别&#xff…

一.MySQL程序简介

整体介绍 1.服务端mysqld(可执行文件) mysqld --verbose --help 2.客户端mysql(可执行文件) 3.其它工具包程序