iOS后台上传

作者: 7b33a23272c4 | 来源:发表于2018-01-09 22:52 被阅读430次

    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__);
    }
    
    

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

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

    上传测试服务器:https://github.com/onezens/fileUploadServer

    demo:https://github.com/onezens/backgroundUploadDemo

    相关文章

      网友评论

      • 青苹果园:我看NSURLSession文档说后台上传,只支持file模式,streams和NSData不支持,博主是否在应用退到后台的时候,测试过你的上传方法是否可行?
      • 690a597c0ae8:form文件流,也就是第二种方法,切换到后台的时候就停了
      • 690a597c0ae8:可否分享一下服务器端的代码?
        7b33a23272c4:@David_f31f 文章末尾有
      • YViVi:第一种服务器怎么获取
        7b33a23272c4:@JaysonGD 看nodejs服务器

      本文标题:iOS后台上传

      本文链接:https://www.haomeiwen.com/subject/nfzegxtx.html