iOS使用NSURLSession实现后台上传

news/2024/7/20 20:15:30 标签: ios

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 YES
        sessionConfig.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的结束情况(成功或者失败)。


http://www.niftyadmin.cn/n/5156102.html

相关文章

代理替换字段

官网 我这里主要是解决代理替换字段的问题,首先我们需要知道 &#xff0c;代理默认会追加字段 vue3vite使用rewrite // 配置前端服务地址和端口server: {hmr: true,// 设置反向代理&#xff0c;跨域proxy: {/api: {target: xxxx,changeOrigin: true},/nerf: {target: xxxx,ch…

开关电源泄漏电流测试方法| 万用表测量开关电源漏电流的方法及接线方式分享

漏电流测试是开关电源安规测试项目之一&#xff0c;目的是为了检测漏电流是否超过了额定标准&#xff0c;防止漏电流过大造成设备损毁&#xff0c;甚至引发电击安全事故。漏电流测试方法多样&#xff0c;纳米软件将带你了解如何用万用表测量开关电源的漏电流。 开关电源漏电流测…

一个例子!教您彻底理解索引的最左匹配原则!

最左匹配原则的定义 简单来讲&#xff1a;在联合索引中&#xff0c;只有左边的字段被用到&#xff0c;右边的才能够被使用到。我们在建联合索引的时候&#xff0c;区分度最高的在最左边。 简单的例子 创建一个表 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, code…

JDBC SQL Server Source Connector: 一览与实践

在快速发展的数据驱动业务环境中&#xff0c;确保数据在各个系统间高效、准确地同步至关重要。为了进一步的数据处理和分析&#xff0c;经常需要将这些数据同步到其他数据处理系统。Apache SeaTunnel 提供了一个强大而灵活的数据集成框架&#xff0c;使得从 SQL Server 到其他系…

软件测试面试看这套全网最权威最全面的800+面试题,你值得拥有

想转行的&#xff0c;想跳槽涨薪的&#xff0c;想换一份更舒服更美好的工作的现在可以准备起来了。 软件测试作为IT届最亲民的门槛最低的准入行业&#xff0c;每年在这个时候会迎来很多的小白&#xff0c;在自学一段时间后&#xff0c;马上面临着面试的压力。 全网最权威最全…

跨境电商商城源码,助力商家全球布局(多语言切换\多货币转换\多商户入驻)

今天&#xff0c;我们要给大家介绍一款强大且多元化的跨境电商解决方案——WoShop跨境电商源码!这款源码拥有许多令人惊叹的功能&#xff0c;其中最引人注目的就是支持多语言切换、多货币转换以及多商户入驻! 设想一下&#xff0c;你是一个跨境电商的卖家&#xff0c;你的业务遍…

蓝鹏测控平台软件 智能制造生产线的大脑

测控软件平台&#xff0c;是由包括底层驱动程序、通讯协议等&#xff0c;集数据采集、自动反馈控制、信息分析以及多种工程应用于一体的一种电子信息处理平台。 蓝鹏测控软件平台目前支持各种文本标签 、数字标签&#xff1b;支持趋势图、波动图、缺陷图及统计图表。多端口实现…

LInux-0.11

文章目录 前言学习资料正文 前言 B站视频链接 linux 0.11 内核代码 学习资料 正文 一个山区512字节