iOS 后台上传&数据同步

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的时候,有几个注意点(坑点)。下面介绍总结的三种不同类型的后台上传方式。

  1. 直接通过文件上传(最简单) 这种上传方式是,网络上最容易搜索到的一种上传方法,直接拿到文件的本地路径,然后进行上传。其中- (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];
}
复制代码
  1. 通过form文件流进行上传 使用这种方式上传form类型的数据的时候有个坑点,那么就是必须要给予两个请求头:Content-LengthContent-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;
    
}
复制代码
  1. 文件流进行上传 这种上传方式对于客户端来讲的话,也是比较方便简单,性能好的一种方法,但是特殊的地方就是服务器需要特殊处理,简单来讲就是有点反服务器的常规。还有请求头和上面一样必须要做处理。
- (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];
}
复制代码
  1. 这里简单聊聊分片上传
  • 首先分片上传,在上面三种方式中你认为哪一种方式可以了?做过分片开发的同学,应该会里面反应过来选择2。是的,在第二种上传方式中,给予了我们自定义上传数据的可能。分片上传与断点续传是有区别的。
  • 在上传和下载中,续传的过程中,每次暂停后上传的过程中,我们会带一个range请求头,这个range才是上传实现续传的关键,文件服务器通过range的范围,来给我们开始的数据流,客户端根据range来拼接本地的缓存文件。
  • 那我们上传的过程中,如何实现分片上传呢?断点续传的区别又是什么?假设我们有200MB的文件,我们单次上传的时候,文件块有点大,那么我们分片上传的时候,会将这个文件进行分块,假设我们分成10块,那么每块就是20MB;接着对分块的数据进行上传,分块的数据一定要有个分块的标号,根据切割的开始为标号0,结束为标号9。分别对这10块数据进行上传,上传的时候将标号带到请求头或者form参数中。
  • 最后文件服务器接收到数据之后,根据标号进行存储到缓存目录,假设文件名为fasadas_0 ~ fasadas_9,客户端上传10个分块结束之后,调用一个文件合并的接口,然后服务器检测分块文件,合并成一个文件,回调成功。

到这里其实还有坑,这时候2,3后台上传还是不起作用,还是有坑,下面继续走

三、上传的代理

  1. 必须实现这个代理,上面的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);
    }
}
复制代码
  1. 上传的进度回调方法
-(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);
}
复制代码
  1. 首个上传任务在后台上传成功的回调
/* 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__);
}
复制代码
  1. AppDelegate中首个后台上传任务在后台完成是的回调
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
    NSLog(@"%s", __func__);
}

复制代码

这几个回调是比较重要的上传回调方法,部分和下面类似,细节处理可查看上传的回调处理

四、有了这些东西没有上传测试服务器请看这里

上传测试服务器:github.com/onezens/fil…

demo:github.com/onezens/bac…

你可能感兴趣的:(移动开发)