现在的位置: 首页 > 综合 > 正文

iOS7 NSURLSession

2017年08月17日 ⁄ 综合 ⁄ 共 7233字 ⁄ 字号 评论关闭

iOS7 Day-by-Day : : Day 1 : : NSURLSession


原文:http://www.shinobicontrols.com/blog/posts/2013/09/20/ios7-day-by-day-day-1-nsurlsession


iOS之前的网络使用NSURLConnection来执行,NSURLConnection使用全局变量来管理cookies和authentication。因此可能会有两个不同的网络连接同时竞争共享的设置。NSURLSession就是用来解决这个问题的,还有一些其他的问题。


配合这篇文章的工程包括三个不同的接下来要讲到的下载情景。这篇文章没有描述整个项目内容—只是讲解了与新NSURLSession API有关的主要部分。


这一系列文章的代码在github的软件仓库中 -github.com/ShinobiControls/iOS7-day-by-day


简单下载


NSURLSession是一个与多个网络连接有关的全局变量。Session变量使用工厂方法创建,这个方法需要传入一个配置对象。有三种类型会话:

1、默认的,进程内会话

2、短暂的(内存中的),进程内会话

3、后台会话

对于一个简单的下载,我只要使用默认的会话就行了:

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];


一旦创建了一个配置对象,它就有一些属性来控制其行为方式。例如,能够来控制TLS(Transport Layer Security,传输层安全协议)安全性的可接受程度,而不管cookie是否被允许或者是请求超时。两个比较有意思的属性是areallowsCellularAccess和discretionary。前者指定了当只有蜂窝网络时是否允许一个设备来运行网络会话。设置一个会话为discretionary使得操作系统能够明智地安排网络访问的次数—例如当WiFi可用时和设备电量充足时。这主要是使用后台会话,因此将默认的会话设置为后台会话。


一旦我们创建了会话配置对象就可以创建这个会话了:

NSURLSession *inProcessSession;

inProcessSession = [NSURLSessionsessionWithConfiguration:sessionConfigdelegate:selfdelegateQueue: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];


这样就可以了—这个会话将会异步地尝试在指定的路径下下载文件。


为了得到请求下载的文件我们需要实现下面的代理方法:


- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(
NSURL *)location

{

   // We've successfully finished the download. Let's save the file

    NSFileManager *fileManager = [NSFileManager
defaultManager];


    NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];

    NSURL *documentsDirectory = URLs[0];


    NSURL *destinationPath = [documentsDirectory URLByAppendingPathComponent:[location lastPathComponent]];

    NSError *error;


   // Make sure we overwrite anything that's already there

    [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中。我们传递了一个下载文件的临时位置,在这段代码里我们把文件保存在了文件目录下,(因为我们已经有了一张图片)然后就可以将它展示给用户了。


上面的这个代理方法只有在下载任务执行成功时才会被调用。下面这个方法在NSURLSessionDelegate中,在每次任务完成时都会被调用,不论它是否成功完成了。


- (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对象可用来创建一个新的任务在后一阶段继续传输数据。如果服务器支持断点续传,那这个数据对象就包括了已经下载的字节:


- (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 = nil。


后台下载


NSURLSession引入的另一个主要的特征是即使app已经没有在运行还能继续进行数据传输。为了实现这点我们要把会话配置成后台会话。


- (NSURLSession *)backgroundSession

{

    static NSURLSession *backgroundSession =nil;

    staticdispatch_once_t
onceToken;

    dispatch_once(&onceToken, ^{

        NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.shinobicontrols.BackgroundDownload.BackgroundSession"];

        backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];

    });

    return backgroundSession;

}


重要的是要记住用一个以后的后台标识只能创建一个会话,因此使用dispatch once block。这个标识的目的是一旦应用重新启动后我们能搞获得到这个会话。创建了一个后台会话就启动了一个后台传输程序,它将为我们管理数据传输。这个后台程序会一直运行即使应用已经被挂起或结束了。


启动一个后台下载任务和我们之前做的完全一样—所有的后台功能都由NSURLSession管理,我们只要创建就行:


NSString *url = @"http://url/for/picture";

NSURLRequest *request = [NSURLRequest
requestWithURL:[
NSURL URLWithString:url]];

self.backgroundTask = [self.backgroundSession
downloadTaskWithRequest:request];

[self.backgrounTask resume];


现在,即使你按了home键来开了应用,下载还是会在后台继续进行。(配置选项的话题在开始时提到了)。


当下载完成时iOS会重新启动你的应用好让人们知道—并且传递有效载荷。


为了这样做它会调用在应用程序代理中的下面这个方法:


- (void)application:(UIApplication
*)application

handleEventsForBackgroundURLSession:(NSString *)identifier

                  completionHandler:(void (^)())completionHandler

{

   self.backgroundURLSessionCompletionHandler
= completionHandler;

}


这里我们传递了一个完成处理器,一旦收到了下载数据并合适地更新UI界面时就要调用它。这里我们正在保存关闭这个完成处理器(记住block一定要是copied的),然后让正在加载的视图控制器来管理数据的处理。当视图控制器加载完后就会创建这个后台会话(它还设置了代理),因此我们之前使用过的同样的代理方法就会被调用:


- (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;

       // Get hold of the app delegate

        SCAppDelegate *appDelegate = (SCAppDelegate *)[[UIApplication sharedApplication] delegate];

       if(appDelegate.backgroundURLSessionCompletionHandler)
{

           // Need to copy the completion handler

           
void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;

            appDelegate.backgroundURLSessionCompletionHandler =nil;

            handler();

        }

    }

}


有几点要注意一下:


1、不能把downloadTash和self.backgroundTask作比较,因为我们无法保证self.backgroundTask是否已经被占据了,因为有可能是应用程序重新启动了。然后比较会话是有效的。

2、这里我们使用了应用的代理,还有其他的方式可以将完成处理器传递到正确的地方。

3、一旦已经完成了文件保存和展示,要确保如果有完成处理器,那要移除它,然后删除。这会告诉操作系统已经处理完了这个下载。


总结


NSURLSession提供了许多新的非常宝贵的特征来处理iOS(还有OSX 10.9)的网络,并且替换掉了做事的旧方法。它很值得掌握并用于所有针对新的操作系的app中。

抱歉!评论已关闭.