当前位置: 首页 > news >正文

iOS使用NSURLSession实现后台上传

NSURLSession后台上传的基本逻辑是:首先创建一个后台模式的NSURLSessionConfiguration,然后通过这个configuration创建一个NSURLSession,接着是创建相关的NSURLSessionTask,最后就是处理相关的代理事件。

1、创建NSURLSession

- (NSURLSession *)backgroundURLSession {static NSURLSession *session = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{NSURLSessionConfiguration* sessionConfig = nil;NSString *identifier = [NSString stringWithFormat:@"%@.%@", [NSBundle mainBundle].bundleIdentifier, @"HttpUrlManager"];sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];//请求的缓存策略sessionConfig.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;//数据传输超时,当恢复传输时会清零sessionConfig.timeoutIntervalForRequest = 60;//单条请求超时,决定一条请求的最长生命周期sessionConfig.timeoutIntervalForResource = 60;//请求的服务类型sessionConfig.networkServiceType = NSURLNetworkServiceTypeDefault;//是否允许使用移动网络(电话网络)default is YESsessionConfig.allowsCellularAccess = YES;//后台模式生效,YES允许自适应系统性能调节sessionConfig.discretionary = YES;session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];});return session;
}

NSURLSessionConfiguration配置有三种模式: 

//默认模式类似于原来的NSURLConnection,可以使用缓存的Cache,Cookie,鉴权
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;//及时模式不使用缓存的Cache,Cookie,鉴权
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;//后台模式在后台完成上传下载,创建Configuration对象的时候需要给一个NSString的ID用于追踪完成工作的Session是哪一个
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier

2、后台上传

- (void)upload:(NSString *)urlStr data:(NSData *)data headers:(NSDictionary *)headers parameters:(NSDictionary *)parameters name:(NSString *)name filename:(NSString *)filename mimeType:(NSString *)mimeType success:(void (^)(id responseObject))success failure:(void (^)(int code, NSString *message))failure {NSURL *url = [NSURL URLWithString:urlStr];NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];request.HTTPMethod = @"POST";NSString *string = [NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", kBoundary];[request setValue:string forHTTPHeaderField:@"Content-Type"];if (headers != nil) {for (NSString *key in headers.allKeys) {[request setValue:headers[key] forHTTPHeaderField:key];}}NSData *bodyData = [self bodyFormData:data parameters:parameters name:name filename:filename mimeType:mimeType];NSString *tempPath = NSTemporaryDirectory();NSTimeInterval interval = [NSDate.now timeIntervalSince1970];NSString *tempName = [NSString stringWithFormat:@"temp%.0f_%@", interval, filename];NSString *tempPath = [tempPath stringByAppendingPathComponent:tempName];[bodyData writeToFile:tempPath atomically:YES];NSURLSession *session = self.backgroundURLSession;NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:tempPath]];[uploadTask resume];
}- (NSData *)bodyFormData:(NSData *)data parameters:(NSDictionary *)parameters name:(NSString *)name filename:(NSString *)filename mimeType:(NSString *)mimeType {if (data == nil || data.length == 0) {return nil;}NSMutableData *formData = [NSMutableData data];NSData *lineData = [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding];NSData *boundary = [[NSString stringWithFormat:@"--%@", kBoundary] dataUsingEncoding:NSUTF8StringEncoding];if (parameters != nil) {[parameters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {[formData appendData:boundary];[formData appendData:lineData];NSString *thisFieldString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n%@", key, obj];[formData appendData:[thisFieldString dataUsingEncoding:NSUTF8StringEncoding]];[formData appendData:lineData];}];}[formData appendData:boundary];[formData appendData:lineData];NSString *thisFieldString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\nContent-Type: %@", name, filename, mimeType];[formData appendData:[thisFieldString dataUsingEncoding:NSUTF8StringEncoding]];[formData appendData:lineData];[formData appendData:lineData];[formData appendData:data];[formData appendData:lineData];[formData appendData:[[NSString stringWithFormat:@"--%@--\r\n", kBoundary] dataUsingEncoding:NSUTF8StringEncoding]];return formData;
}

 上传有4种方法:

/* Creates an upload task with the given request.  The body of the request will be created from the file referenced by fileURL */
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;/* Creates an upload task with the given request.  The body of the request is provided from the bodyData. */
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

后台模式不支持使用带回调的上传方法,否则会报错:

Completion handler blocks are not supported in background sessions. Use a delegate instead.

后台模式不支持使用NSData的上传方法,否则会报错:

Upload tasks from NSData are not supported in background sessions 

所以如果使用后台模式上传,选择uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL方法。

NSURLSessionDataDelegate上传代理事件

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {NSLog(@"URLSession didSendBodyData progress: %f" ,totalBytesSent/(float)totalBytesExpectedToSend);
}- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {NSLog(@"%s", __func__);
}- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {NSMutableData *responseData = self.responsesData[@(dataTask.taskIdentifier)];if (!responseData) {responseData = [NSMutableData dataWithData:data];self.responsesData[@(dataTask.taskIdentifier)] = responseData;} else {[responseData appendData:data];}
}- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {if (error) {NSLog(@"URLSession didCompleteWithError %@ failed: %@", task.originalRequest.URL, error);}NSMutableData *responseData = self.responsesData[@(task.taskIdentifier)];if (responseData) {NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];if (response) {NSLog(@"response = %@", response);} else {NSString *errMsg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];NSLog(@"responseData = %@", errMsg);}[self.responsesData removeObjectForKey:@(task.taskIdentifier)];} else {NSLog(@"responseData is nil");}
}

3、后台请求

- (void)request:(NSString *)urlStr method:(NSString *)method headers:(NSDictionary *)headers parameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success failure:(void (^)(int code, NSString *message))failure {urlStr = [self getFullUrlString:urlStr parameters:parameters];NSURL *url = [NSURL URLWithString:urlStr];NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];request.HTTPMethod = method;if (headers != nil) {for (NSString *key in headers.allKeys) {[request setValue:headers[key] forHTTPHeaderField:key];}}NSURLSession *session = self.backgroundURLSession;NSURLSessionDataTask *task = [session dataTaskWithRequest:request];[task resume];
}- (NSString *)getFullUrlString:(NSString *)urlStr parameters:(NSDictionary *)parameters {NSMutableString *newStr = [NSMutableString stringWithString:urlStr];if (parameters.allKeys.count > 0) {BOOL isFirst = NO;for (NSString *key in parameters) {isFirst = YES;[newStr appendString:isFirst?@"?":@"&"];[newStr appendFormat:@"%@=%@", key, parameters[key]];}}return newStr;
}

4、Session和ApplicationDelegate交互

使用BackgroundSession后台模式,在Task执行的时候,当用户切到后台时,Session会和ApplicationDelegate做交互,在BackgroundSession中的Task还会继续下载/上传。

现在分三个场景分析下Session和Application的关系:

(1)当加入了多个Task,程序没有切换到后台。 

这种情况Task会按照NSURLSessionConfiguration的设置正常下载,不会和ApplicationDelegate有交互。

(2)当加入了多个Task,程序切到后台,所有Task都完成下载

在切到后台之后,Session的Delegate不会再收到,Task相关的消息,直到所有Task全都完成后,系统会调用ApplicationDelegate的application:handleEventsForBackgroundURLSession:completionHandler:回调,之后“汇报”下载工作,对于每一个后台下载的Task调用Session的Delegate中的URLSession:downloadTask:didFinishDownloadingToURL:(成功的话)和URLSession:task:didCompleteWithError:(成功或者失败都会调用)。 

AppDelegate:

@property (copy, nonatomic) void(^backgroundSessionCompletionHandler)();- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {self.backgroundSessionCompletionHandler = completionHandler;
}

Session的Delegate

@interface MyViewController()<NSURLSessionDelegate>
@end@implementation MyViewController- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];if (appDelegate.backgroundSessionCompletionHandler) {void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;appDelegate.backgroundSessionCompletionHandler = nil;completionHandler();}NSLog(@"All tasks are finished");
}@end

(3)当加入了多个Task,程序切到后台,下载完成了几个Task,然后用户又切换到前台。(程序没有退出)

切到后台之后,Session的Delegate仍然收不到消息。在下载完成几个Task之后再切换到前台,系统会先汇报已经下载完成的Task的情况,然后继续下载没有下载完成的Task,后面的过程同第一种情况。 

(4)当加入了多个Task,程序切到后台,几个Task已经完成,但还有Task还没有下载完的时候关掉强制退出程序,然后再进入程序的时候。(程序退出了)

由于程序已经退出了,后面没有下完Session就不在了后面的Task肯定是失败了。但是已经下载成功的那些Task,新启动的程序也没有听“汇报”的机会了。经过实验发现,这个时候之前在NSURLSessionConfiguration设置的NSString类型的ID起作用了,当ID相同的时候,一旦生成Session对象并设置Delegate,马上可以收到上一次关闭程序之前没有汇报工作的Task的结束情况(成功或者失败)。

相关文章:

iOS使用NSURLSession实现后台上传

NSURLSession后台上传的基本逻辑是&#xff1a;首先创建一个后台模式的NSURLSessionConfiguration&#xff0c;然后通过这个configuration创建一个NSURLSession&#xff0c;接着是创建相关的NSURLSessionTask&#xff0c;最后就是处理相关的代理事件。 1、创建NSURLSession -…...

linux之信号

Linux之信号 什么是信号信号的产生方式signalsignactionkill信号集信号屏蔽 什么是信号 信号机制是一种使用信号来进行进程之间传递消息的方法&#xff0c;信号的全称为软中断信号&#xff0c;简称软中断。 信号的本质是软件层次上对中断的一种模拟&#xff08;软中断&#xff…...

golang工程中间件——redis常用结构及应用(string, hash, list)

Redis 命令中心 【golang工程中间件——redisxxxxx】这些篇文章专门以应用为主&#xff0c;原理性的后续博主复习到的时候再详细阐述 string结构以及应用 字符数组&#xff0c;redis字符串是二进制安全字符串&#xff0c;可以存储图片等二进制数据&#xff0c;同时也可以存…...

Java中数据结构(基本数据类型+引用数据类型)介绍+整理+例子+对比

一、Java数据类型分类 在Java中&#xff0c;数据类型可以分为两大类&#xff1a;内置数据类型&#xff08;Primitive Data Types&#xff09;和引用数据类型&#xff08;Reference Data Types&#xff09;。 **内置数据类型&#xff08;Primitive Data Types&#xff09;**是…...

SpringSecurity原理

Spring Security是Spring框架中的一个安全性框架&#xff0c;用于保护Web应用程序。以下是Spring Security的工作原理&#xff1a; 1.认证 认证是指验证用户身份。Spring Security使用过滤器链来拦截用户的请求。在对请求进行处理之前&#xff0c;它需要对用户进行认证。Spri…...

云表平台突破传统,企业级低代码让软件开发速度提升

随着数字化进程的加速推进&#xff0c;软件开发效率和成本的要求也在日益提高。在这个背景下&#xff0c;低代码技术的出现为企业软件开发提供了新的解决方案。低代码开发平台以其简单易用、高效灵活的特点&#xff0c;已经成为各行各业企业进行应用开发的首选工具。 企业中低代…...

三数之和(双指针)

15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三…...

Linux-bluetooth蓝牙

蓝牙配对和蓝牙连接 蓝牙配对是指在两个蓝牙设备之间建立一种安全的关系&#xff0c;以确保只有已经通过授权的设备才能进行通信。在蓝牙配对过程中&#xff0c;设备之间将共享一个加密密钥&#xff0c;用于保护数据传输的安全性。通常需要在设备上输入一个PIN码或者进行手动确…...

mediasoup webrtc音视频会议搭建

环境ubuntu22.10 nvm --version 0.33.11 node -v v16.20.2 npm -v 8.19.4 node-gyp -v v10.0.1 python3 --version Python 3.10.7 python with pip: sudo apt install python3-pip gcc&g version 12.2.0 (Ubuntu 12.2.0-3ubuntu1) Make 4.2.1 npm install mediasoup3 sudo …...

【操作系统】操作系统的大端模式和小端模式

什么是大端模式、小端模式&#xff1f; 所谓的大端模式&#xff0c;是指数据的低位保存在内存的高地址中&#xff0c;而数据的高位保存在内存的低地址中&#xff1b; 所谓的小端模式&#xff0c;是指数据的低位保存在内存的低地址中&#xff0c;而数据的高位保存在内存的高地…...

Oracle(13)Maintaining Data Integrity

目录 一、基础知识 1、Data Integrity 数据库的完整性 2、Types of Constraints 约束类型 3、Constraint States 约束状态 4、Guidelines for Constraints 约束准则 二、基础操作 1、Enabling Constraints 启用约束 2、命令方式创建约束 3、修改表创建的约束 4、删除约…...

工程(十二)Ubuntu20.04LSD_SLAM运行

博主创建了一个科研互助群Q&#xff1a;772356582&#xff0c;欢迎大家加入讨论。这是一个科研互助群&#xff0c;主要围绕机器人&#xff0c;无人驾驶&#xff0c;无人机方面的感知定位&#xff0c;决策规划&#xff0c;以及论文发表经验&#xff0c;以方便大家很好很快的科研…...

跨境电商,用指纹浏览器还是VPS?有何区别?

目前做跨境电商的小伙伴基本都是选择vps或者指纹浏览器来防关联。不过随着指纹浏览器的普及&#xff0c;越来越多人选择使用指纹浏览器&#xff0c;还没了解过指纹浏览器的小伙伴可能还在犹豫&#xff0c;vps和指纹浏览器到底哪个更好呢&#xff1f; Vps就是一个虚拟服务器&…...

R语言piecewiseSEM结构方程模型在生态环境领域实践技术应用

结构方程模型&#xff08;Sructural Equation Modeling&#xff0c;SEM&#xff09;可分析系统内变量间的相互关系&#xff0c;并通过图形化方式清晰展示系统中多变量因果关系网&#xff0c;具有强大的数据分析功能和广泛的适用性&#xff0c;是近年来生态、进化、环境、地学、…...

一站式解决方案:体验亚马逊轻量服务器/VPS的顶级服务与灵活性

文章目录 一、什么是轻量级服务器/VPS 二、服务器创建步骤 三、服务器连接客户端(私钥登录) 四、使用服务器搭建博客网站 五、个人浅解及总结 一、什么是轻量级服务器/VPS 亚马逊推出的轻量级服务器/VPS&#xff1a;是一种基于云计算技术的虚拟服务器解决方案。它允许用户…...

pda条码二维码扫描数据采集安卓手持终端扫码热敏标签打印一体机

HT800新一代移动物联终端是深圳联强优创信息科技有限公司自主研发的基于Android11操作系统的高性能、高可靠的工业级手持数据终端&#xff0c;能与其它设备进行无线通讯&#xff0c;提供良好的操作界面&#xff0c;支持条码扫描、RFID读写&#xff08;NFC&#xff09;、GPS定位…...

白上这么多年班,才知道数据可视化这么简单

写编程整理数据、做数据可视化分析&#xff0c;不仅难度大、易僵化&#xff0c;还效率低&#xff0c;不能及时响应业务的数据分析需求。那怎么办&#xff1f;换个BI数据可视化工具&#xff0c;套用BI方案&#xff0c;数据分析模型、BI数据可视化分析报表都一应俱全&#xff0c;…...

伊朗黑客对以色列科技和教育领域发起破坏性网络攻击

导语 近期&#xff0c;以色列的高等教育和科技领域遭受了一系列破坏性的网络攻击。这些攻击始于2023年1月&#xff0c;旨在部署以前未记录的数据清除恶意软件。在最近的攻击中&#xff0c;攻击者试图窃取个人身份信息和知识产权等敏感数据。本文将介绍这些攻击的具体细节&#…...

前端初始化项目切换镜像命令

不切换成国内镜像容易出现&#xff1a; idealTree:moni: sill idealTree buildDeps 一直卡着 命令如下&#xff1a; 一、 npm config get registry&#xff0c;查看当前镜像地址 二、出现 https://registry.npmjs.org/ 则表示在国外 三、使用以下命令切换成国内阿里…...

Springboot中解析JSON字符串(jackson库ObjectMapper解析JSON字符串)

1、ObjectMapper与JSONObject比较 1、ObjectMapper属于jackson库的一部分,JSONObject属于alibaba的fastjson&#xff0c;两者各有优劣&#xff0c;可根据自己的系统环境选择使用哪种技术。 2、目前来看&#xff0c;Jackson社区相对活跃&#xff0c;Spring MVC和Spring Boot都…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...