iOS NSURLSession 详解

来源:互联网 发布:saas多租户数据库设计 编辑:程序博客网 时间:2024/05/16 18:44

最近公司项目中,之前做的上传下载列表被用户吐槽,不能后台下载,不能锁屏下载。于是就开始寻找解决办法。
因为在iOS7 就推出了NSURLSession ,我也知道它能够实现后台下载。(之前一个哥们在做某视频软件时的需求就是要求后台也可以下载)。
我就直接定位到了NSURLSession,开始着手重写app的上传下载模块。找了资料研究了一下NSURLSession。
资料:https://www.shinobicontrols.com/blog/ios7-day-by-day-day-1-nsurlsession
NSURLSession提供的功能:
1、通过URL将数据下载到内存
2、通过URL将数据下载到文件系统
3、将数据上传到指定URL
4、在后台完成上述功能
NSURLSession状态同时对应着多个连接,不像之前使用共享的一个全局状态。会话是通过工厂方法来创建配置对象。
总共有三种会话:
1. 默认的,进程内会话
2. 短暂的(内存),进程内会话
3. 后台会话
如果是简单的下载,我们只需要使用默认模式即可:
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
配置对象有很多属性。例如,可以设置TLS安全等级,TLS决定你可以使用cookies和超时时间。还有两个非常有趣的属性:allowsCellularAccess和discretionary。前一个属性表示当只有一个3G网络时,网络是否允许访问。设置 discretionary属性可以控制系统在一个合适的时机访问网络,比如有可用的WiFi,有充足的电量。这个属性主要针对后台回话的,所以在后台会话模式下默认是打开的。
当我们创建了一个会话配置对象后,就可以用它来创建会话对象了:   

NSURLSession *inProcessSession;inProcessSession = [NSURLSession sessionWithConfiguration:sessionConfig                                       delegate:self delegateQueue:nil];
注意:这里我们把自己设置为代理了。通过代理方法可以告诉我们数据传输进度以及获取认证信息。下面我们会实现一些合适的代理。

数据传输时封装在任务里面的,这里有三种类型:
1. 数据任务 (NSURLSessionDataTask)
2. 上传任务 (NSURLSessionUploadTask)
3. 下载任务(NSURLSessionDownloadTask)
在会话中传输数据时,我们需要实现某一种任务。比如下载:
NSString *url = @"http://appropriate/url/here";NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];NSURLSessionDownloadTask *cancellableTask =                               [inProcessSession downloadTaskWithRequest:request];[cancellableTask resume];
现在会话将会异步下载此url的文件内容。

我们需要实现一个代理方法来获取这个下载的内容:


-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{    NSFileManager *fileManager = [NSFileManager defaultManager];    NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];    NSURL *documentsDirectory = URLs[0];    NSURL *destinationPath =    [documentsDirectory URLByAppendingPathComponent:[location lastPathComponent]];        NSError *error;    [fileManager removeItemAtURL:destinationPath error:NULL];        BOOL success = [fileManager copyItemAtURL:location toURL:destinationPath error:&error];    if (success){                dispatch_async(dispatch_get_main_queue(), ^{            UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]]; self.imageView.image = image;            self.imageView.contentMode = UIViewContentModeScaleAspectFill;                        self.imageView.hidden = NO;        });    }    else {        NSLog(@"Couldn't copy the downloaded file");    }        if(downloadTask == cancellableTask)            {                cancellableTask = nil;            }    }
这个方法在NSURLSessionDownloadTaskDelegate代理中。在代码中,我们获取到下载文件的临时目录,并把它保存到文档目录下(因为有个图片),然后显示给用户。

上面的代理是下载成功的回调方法。下面代理方法也在NSURLSessionDownloadTaskDelegate代理中,不管任务是否成功,在完成后都会回调这个代理方法。


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{    dispatch_async(dispatch_get_main_queue(), ^{        self.progressIndicator.hidden = YES;    });}
如果error是nil,则证明下载是成功的,否则就要通过它来查询失败的原因。如果下载了一部分,这个error会包含一个NSData对象,如果后面要恢复任务可以用到。

传输进度
上一节结尾,你可能注意到我们有一个进度来标示每个任务完成度。更新进度条可能不是很容易,会有一个额外的代理来做这件事情,当然它会被调用多次。


-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten BytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{    double currentProgress = totalBytesWritten / (double)totalBytesExpectedToWrite; dispatch_async(dispatch_get_main_queue(), ^{        self.progressIndicator.hidden = NO; self.progressIndicator.progress = currentProgress;    });}
这是NSURLSessionDownloadTaskDelegate的另一个代理方法,我们用来计算进度并更新进度条。

取消下载
NSURLConnection一旦发送是没法取消的。但是,我们可以很容易的取消掉一个NSURLSessionTask任务:
- (IBAction)cancelCancellable:(id)sender{    if(cancellableTask)    {        [cancellableTask cancel];        cancellableTask = nil;    }}
非常容易!当取消后,会回调这个URLSession:task:didCompleteWithError:代理方法,通知你去及时更新UI。当取消一个任务后,也十分可能会再一次回调这个代理方法 URLSession:downloadTask:didWriteData:BytesWritten:totalBytesExpectedToWrite: 。当然,didComplete 方法肯定是最后一个回调的。

恢复下载
恢复下载也非常容易。这里重写了个取消方法,会生成一个NSData对象,可以在以后用来继续下载。如果服务器支持恢复下载,这个data对象会包含已经下载了的内容。


- (IBAction)cancelCancellable:(id)sender{    if(self.resumableTask)    {        [self.resumableTask cancelByProducingResumeData:^(NSData *resumeData)         {             partialDownload = resumeData; self.resumableTask = nil;         }];    }}
上面方法中,我们把待恢复的数据保存到一个变量中,方便后面恢复下载使用。
当新创建一个下载任务的时候,除了使用一个新的请求,我们也可以使用待恢复的下载数据:
if(!self.resumableTask){    if(partialDownload)            {        self.resumableTask = [inProcessSession downloadTaskWithResumeData:partialDownload];    }        else           {        NSString *url = @"http://url/for/image";        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.resumableTask = [inProcessSession downloadTaskWithRequest:request];    }    [self.resumableTask resume];}
如果我们有这个partialDownload这个数据对象,就可以用它来创建一个新的任务。如果没有,就按以前的步骤来创建任务。
记住:当使用partialDownload创建任务完成后,需要把partialDownload设置为nil。

后台下载
NSURLSession另一个重要的特性:即使当应用不在前台时,你也可以继续传输任务。当然,我们的会话模式也要为后台模式:
- (NSURLSession *)backgroundSession{    static NSURLSession *backgroundSession = nil;    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.shinobicontrols.BackgroundDownload.BackgroundSession"];        backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; });    return backgroundSession;}
需要非常注意的是,通过给的后台token,我们只能创建一个后台会话,所以这里使用dispatch once block。token的目的是为了当应用重启后,我们可以通过它获取会话。创建一个后台会话,会启动一个后台传输守护进程,这个进程会管理数据并传输给我们。即使当应用挂起或者终止,它也会继续运行。

开启后台下载任务和之前一样,所有的后台功能都是NSURLSession自己管理的。
NSString *url = @"http://url/for/picture";NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request]; [self.backgrounTask resume];
现在,即使你按home键离开应用,下载也会在后台继续(受开始提到的配置项控制)。

当下载完成后,你的应用将被重启,并传输内容过来。
将会调用app delegate的这个方法:
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{    self.backgroundURLSessionCompletionHandler = completionHandler;}
这里,我们获取内容通过completionHandler,当我们接收下载的数据并更新UI时会调用completionHandler。我们保存了 completionHandler(注意需要copy),让正在加载的View Controller来处理数据。当View Controller加载成功后,创建后台会话(并设置代理)。因此之前使用的相同代理方法就会被调用。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ // Save the file off as before, and set it as an image view//...    if (session == self.backgroundSession)    {        self.backgroundTask = nil;        SCAppDelegate *appDelegate = (SCAppDelegate *)[[UIApplication sharedApplication] delegate];        if(appDelegate.backgroundURLSessionCompletionHandler)        { // Need to copy the completion handlervoid (^handler)() = appDelegate.backgroundURLSessionCompletionHandler; appDelegate.backgroundURLSessionCompletionHandler = nil;            handler();        }    }}
需要注意的几个地方:
1. 不能用downloadTask和self.backgroundTask来比较。因为我们不能确定self.backgroundTask是不是已经有了,有可能是应用新的一次重启。比较session是可行的。
2. 这里使用app delegate来获取completion handler 的。其实,有很多方式来获取completion handler 的。
3. 当保存完文件并显示完成后,如果有completion handler,需要移除然后调用。这个是为了告诉系统我们已经完成了,可以处理新的下载任务了。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 苹果7耳机转换器不支持怎么办 华为mate10耳机声音小怎么办 200打一年到期了怎么办 手机欠费变成2g怎么办 手机4g网用不了怎么办 手机玩王者荣耀卡怎么办 华为隐私空间密码忘记了怎么办 华为手机王者太卡怎么办 华为手机太卡怎么办呢 华为手机5x太卡!怎么办 华为手机玩游戏时太卡了怎么办? 买到华为翻新机怎么办 华为畅享8声音小怎么办 华为5a安全模式怎么办 手机跳屏怎么办金立 苹果手机触控不灵敏怎么办 华为mate8手机声音小怎么办 华为mate9相机无法对焦怎么办 新疆外地电信卡信号差怎么办 华为手机搜索不到wifi怎么办 华为浏览器恢复只有一个页面怎么办 华为手机触摸屏没反应怎么办 快递不给送上楼怎么办 华为荣耀手机声音小怎么办 华为手机来电铃声小怎么办 s弯出来时老压线怎么办 苹果7p手机弯了怎么办 小米手机摔弯了怎么办 华为畅享5没声音怎么办 掌阅语音闪退怎么办 华为mate开屏成排线怎么办 华为mate8电池坏了怎么办 8plus拍照不清晰怎么办 荣耀手环3丢了怎么办 华为mate9手机声音小怎么办 华为麦芒6丢了怎么办 华为麦芒4无法访问移动网络怎么办 自拍时屏幕是白的怎么办 华为麦芒5手机音量小怎么办 小米5x玩王者卡怎么办 小米5x打王者卡怎么办