iOS后台上传
2018-01-09 本文已影响430人
7b33a23272c4
iOS 后台上传的处理逻辑,大概和后台下载的逻辑相差无几。基本的逻辑是:首先创建一个NSURLSessionConfiguration
,然后通过这个configuration,创建一个NSURLSession
,接着是创建相关的NSURLSessionTask
,最后就是处理相关的回调方法。
一、创建NSURLSession
创建一个后台下载的session
- (NSURLSession *)getDownloadURLSession {
NSString *identifier = [self backgroundSessionIdentifier];
NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
return session;
}
获取后台下载的标识
- (NSString *)backgroundSessionIdentifier {
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
NSString *identifier = [NSString stringWithFormat:@"%@.BackgroundSession", bundleId];
return identifier;
}
二、创建上传的NSURLSessionTask
NSURLSessionUploadTask 的父类是NSURLSessionDataTask,它的父类是NSURLSessionTask。在创建上传的task的时候,有几个注意点(坑点)。下面介绍总结的三种不同类型的后台上传方式。
- 直接通过文件上传(最简单)
这种上传方式是,网络上最容易搜索到的一种上传方法,直接拿到文件的本地路径,然后进行上传。其中- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
不支持后台上传。
- (void)bgUploadFromFile{
NSLog(@"%s", __func__);
NSURL *url = [NSURL URLWithString:kStreamUploadUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *path = [[NSBundle mainBundle] pathForResource:@"icon.jpg" ofType:nil];
self.uploadTask = [self.backgroundSession uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:path]];
[self.uploadTask resume];
}
- 通过form文件流进行上传
使用这种方式上传form类型的数据的时候有个坑点,那么就是必须要给予两个请求头:Content-Length
和Content-Type
,没有这两个请求头,点击是没有反应的!
- (void)bgUploadStreamForm
{
NSLog(@"%s", __func__);
NSURL *url = [NSURL URLWithString:kFormUploadUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *path = [[NSBundle mainBundle]pathForResource:@"icon" ofType:@"jpg"];
NSData *bodydata = [self buildBodyDataWithPicPath:path];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; charset=utf-8;boundary=%@",boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
[request setValue:[NSString stringWithFormat:@"%zd", bodydata.length] forHTTPHeaderField:@"Content-Length"];
request.HTTPBodyStream = [NSInputStream inputStreamWithData:bodydata];
self.uploadTask = [self.backgroundSession uploadTaskWithStreamedRequest:request];
[self.uploadTask resume];
}
拼接form类型的数据,上传中需要的额外参数,也可以在form当中拼接,提交给服务器
-(NSData*)buildBodyDataWithPicPath:(NSString *)path{
NSMutableData *bodyData = [NSMutableData data];
NSMutableString *bodyStr = [NSMutableString string];
[bodyStr appendFormat:@"--%@\r\n",boundary];//\n:换行 \n:切换到行首
[bodyStr appendFormat:@"Content-Disposition: form-data; name=\"sampleFile\"; filename=\"icon.jpg\""];
[bodyStr appendFormat:@"\r\n\r\n"];
NSData *start = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
[bodyData appendData:start];
NSData *picData = [NSData dataWithContentsOfFile:path];
[bodyData appendData:picData];
bodyStr = [NSMutableString string];
[bodyStr appendFormat:@"\r\n--%@--",boundary];
NSData *endData = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
[bodyData appendData:endData];
return bodyData;
}
- 文件流进行上传
这种上传方式对于客户端来讲的话,也是比较方便简单,性能好的一种方法,但是特殊的地方就是服务器需要特殊处理,简单来讲就是有点反服务器的常规。还有请求头和上面一样必须要做处理。
- (void)bgUploadStreamFile {
NSLog(@"%s", __func__);
NSURL *url = [NSURL URLWithString:kStreamUploadUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *path = [[NSBundle mainBundle]pathForResource:@"icon" ofType:@"jpg"];
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSDictionary *fileAttri = [fileMgr attributesOfItemAtPath:path error:nil];
NSNumber *fileSize = [fileAttri valueForKey:@"NSFileSize"];
request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:path];
[request setValue:[NSString stringWithFormat:@"%zd", fileSize.integerValue] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
self.uploadTask = [self.backgroundSession uploadTaskWithStreamedRequest:request];
[self.uploadTask resume];
}
- 这里简单聊聊分片上传
- 首先分片上传,在上面三种方式中你认为哪一种方式可以了?做过分片开发的同学,应该会里面反应过来选择2。是的,在第二种上传方式中,给予了我们自定义上传数据的可能。分片上传与断点续传是有区别的。
- 在上传和下载中,续传的过程中,每次暂停后上传的过程中,我们会带一个
range
请求头,这个range才是上传实现续传的关键,文件服务器通过range的范围,来给我们开始的数据流,客户端根据range来拼接本地的缓存文件。 - 那我们上传的过程中,如何实现分片上传呢?断点续传的区别又是什么?假设我们有200MB的文件,我们单次上传的时候,文件块有点大,那么我们分片上传的时候,会将这个文件进行分块,假设我们分成10块,那么每块就是20MB;接着对分块的数据进行上传,分块的数据一定要有个分块的标号,根据切割的开始为标号0,结束为标号9。分别对这10块数据进行上传,上传的时候将标号带到请求头或者form参数中。
- 最后文件服务器接收到数据之后,根据标号进行存储到缓存目录,假设文件名为fasadas_0 ~ fasadas_9,客户端上传10个分块结束之后,调用一个文件合并的接口,然后服务器检测分块文件,合并成一个文件,回调成功。
到这里其实还有坑,这时候2,3后台上传还是不起作用,还是有坑,下面继续走
三、上传的代理
- 必须实现这个代理,上面的2,3方式的上传才能够进行
/* Sent if a task requires a new, unopened body stream. This may be
* necessary when authentication has failed for any request that
* involves a body stream.
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler {
NSInputStream *inputStream = task.originalRequest.HTTPBodyStream ;;
if (completionHandler) {
completionHandler(inputStream);
}
}
- 上传的进度回调方法
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
NSLog(@"progress: %f" ,totalBytesSent/(float)totalBytesExpectedToSend);
}
- 首个上传任务在后台上传成功的回调
/* If an application has received an
* -application:handleEventsForBackgroundURLSession:completionHandler:
* message, the session delegate will receive this message to indicate
* that all messages previously enqueued for this session have been
* delivered. At this time it is safe to invoke the previously stored
* completion handler, or to begin any internal updates that will
* result in invoking the completion handler.
*/
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
NSLog(@"%s", __func__);
}
- AppDelegate中首个后台上传任务在后台完成是的回调
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
NSLog(@"%s", __func__);
}
这几个回调是比较重要的上传回调方法,部分和下面类似,细节处理可查看上传的回调处理