zoukankan      html  css  js  c++  java
  • NSURLSession学习笔记

    NSURLSession学习笔记(一)简介

    一、URL Session的基本概念

    1.三种工作模式:

    默认会话模式(default):工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。

    瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。

    后台会话模式(background):该模式在后台完成上传和下载,在创建Configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话。

    2.NSURLSession支持的三种任务

    NSURLSession类支持三种类型的任务:加载数据,下载和上传。

    二、相关的类

    NSURLConnection这个名字,实际上指的是一组构成Foundation框架中URL加载系统的相互关联的组件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache,NSHTTPCookieStorage,NSURLCredentialStorage,以及和它同名的NSURLConnection。

    在WWDC 2013中,Apple的团队对NSURLConnection进行了重构,并推出了NSURLSession作为替代。

    NSURLSession也是一组相互依赖的类,它的大部分组件和NSURLConnection中的组件相同如NSURLRequest,NSURLCache等。而NSURLSession的不同之处在于,它将NSURLConnection替换为NSURLSession和NSURLSessionConfiguration,以及3个NSURLSessionTask的子类:NSURLSessionDataTask, NSURLSessionUploadTask, 和NSURLSessionDownloadTask。

    下面来说下NSURLSession新推出的类:

    1.NSURLSessionConfiguration类

    其中NSURLSessionConfiguration用于配置会话的属性,可以通过该类配置会话的工作模式:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. + (NSURLSessionConfiguration *)defaultSessionConfiguration;  
    2. + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;  
    3. + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;  


    在backgroundSessionConfiguration:方法中的identifier参数指定了会话的ID,用于标记后台的session。

    该类的其中两个属性:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* allow request to route over cellular. */  
    2. @property BOOL allowsCellularAccess;  
    3.   
    4. /* allows background tasks to be scheduled at the discretion of the system for optimal performance. */  
    5. @property (getter=isDiscretionary) BOOL discretionary NS_AVAILABLE(NA, 7_0);  


    allowsCellularAccess 属性指定是否允许使用蜂窝连接, discretionary属性为YES时表示当程序在后台运作时由系统自己选择最佳的网络连接配置,该属性可以节省通过蜂窝连接的带宽。在使用后台传输数据的时候,建议使用discretionary属性,而不是allowsCellularAccess属性,因为它会把WiFi和电源可用性考虑在内。补充:这个标志允许系统为分配任务进行性能优化。这意味着只有当设备有足够电量时,设备才通过Wifi进行数据传输。如果电量低,或者只仅有一个蜂窝连接,传输任务是不会运行的。后台传输总是在discretionary模式下运行。

    2.NSURLSession类

    获取NSURLSession类对象有几种方式:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * The shared session uses the currently set global NSURLCache, 
    3.  * NSHTTPCookieStorage and NSURLCredentialStorage objects. 
    4.  */  
    5. + (NSURLSession *)sharedSession;  
    6.   
    7. /* 
    8.  * Customization of NSURLSession occurs during creation of a new session. 
    9.  * If you only need to use the convenience routines with custom 
    10.  * configuration options it is not necessary to specify a delegate. 
    11.  * If you do specify a delegate, the delegate will be retained until after 
    12.  * the delegate has been sent the URLSession:didBecomeInvalidWithError: message. 
    13.  */  
    14. + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;  
    15. + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;  


    第一种方式是使用静态的sharedSession方法,该类使用共享的会话,该会话使用全局的Cache,Cookie和证书。

    第二种方式是通过sessionWithConfiguration:方法创建对象,也就是创建对应配置的会话,与NSURLSessionConfiguration合作使用。

    第三种方式是通过sessionWithConfiguration:delegate:delegateQueue方法创建对象,二三两种方式可以创建一个新会话并定制其会话类型。该方式中指定了session的委托和委托所处的队列。当不再需要连接时,可以调用Session的invalidateAndCancel直接关闭,或者调用finishTasksAndInvalidate等待当前Task结束后关闭。这时Delegate会收到URLSession:didBecomeInvalidWithError:这个事件。Delegate收到这个事件之后会被解引用。

    3.NSURLSessionTask类

    NSURLSessionTask是一个抽象子类,它有三个子类:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。这三个类封装了现代应用程序的三个基本网络任务:获取数据,比如JSON或XML,以及上传和下载文件。

    下面是其继承关系:

    有多种方法创建对应的任务对象:

    (1)NSURLSessionDataTask

    通过request对象或url创建:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* Creates a data task with the given request.  The request may have a body stream. */  
    2. - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;  
    3.   
    4. /* Creates a data task to retrieve the contents of the given URL. */  
    5. - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;  


    通过request对象或url创建,同时指定任务完成后通过completionHandler指定回调的代码块:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * data task convenience methods.  These methods create tasks that 
    3.  * bypass the normal delegate calls for response and data delivery, 
    4.  * and provide a simple cancelable asynchronous interface to receiving 
    5.  * data.  Errors will be returned in the NSURLErrorDomain,  
    6.  * see <Foundation/NSURLError.h>.  The delegate, if any, will still be 
    7.  * called for authentication challenges. 
    8.  */  
    9. - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  
    10. - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  


    (2)NSURLSessionUploadTask

    通过request创建,在上传时指定文件源或数据源。

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* Creates an upload task with the given request.  The body of the request will be created from the file referenced by fileURL */  
    2. - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;  
    3.   
    4. /* Creates an upload task with the given request.  The body of the request is provided from the bodyData. */  
    5. - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;  
    6.   
    7. /* Creates an upload task with the given request.  The previously set body stream of the request (if any) is ignored and the URLSession:task:needNewBodyStream: delegate will be called when the body payload is required. */  
    8. - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;  


    在创建upload task对象时,通过completionHandler指定任务完成后的回调代码块:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * upload convenience method. 
    3.  */  
    4. - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  
    5. - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  

    (3)NSURLSessionDownloadTask

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* Creates a download task with the given request. */  
    2. - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;  
    3.   
    4. /* Creates a download task to download the contents of the given URL. */  
    5. - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;  
    6.   
    7. /* Creates a download task with the resume data.  If the download cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */  
    8. - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;  


    下载任务支持断点续传,第三种方式是通过之前已经下载的数据来创建下载任务。
    同样地可以通过completionHandler指定任务完成后的回调代码块:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 
    2.  * download task convenience methods.  When a download successfully 
    3.  * completes, the NSURL will point to a file that must be read or 
    4.  * copied during the invocation of the completion routine.  The file 
    5.  * will be removed automatically. 
    6.  */  
    7. - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;  
    8. - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;  
    9. - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;  



    4.NSURLSessionDelegate和NSURLSessionTaskDelegate协议

    在协议的方法中可以完成各种各样的回调动作,如身份验证、完成任务后的动作、错误处理和后台任务完成的动作等。委托方法指定在NSURLSession中一定数量的字节传输使用int64_t类型的参数。

    这里只说下后台任务的一个委托方法:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* If an application has received an 
    2.  * -application:handleEventsForBackgroundURLSession:completionHandler: 
    3.  * message, the session delegate will receive this message to indicate 
    4.  * that all messages previously enqueued for this session have been 
    5.  * delivered.  At this time it is safe to invoke the previously stored 
    6.  * completion handler, or to begin any internal updates that will 
    7.  * result in invoking the completion handler. 
    8.  */  
    9. - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session NS_AVAILABLE_IOS(7_0);  

    合作使用的ApplicationDelegate方法:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. // Applications using an NSURLSession with a background configuration may be launched or resumed in the background in order to handle the  
    2. // completion of tasks in that session, or to handle authentication. This method will be called with the identifier of the session needing  
    3. // attention. Once a session has been created from a configuration object with that identifier, the session's delegate will begin receiving  
    4. // callbacks. If such a session has already been created (if the app is being resumed, for instance), then the delegate will start receiving  
    5. // callbacks without any action by the application. You should call the completionHandler as soon as you're finished handling the callbacks.  
    6. - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler NS_AVAILABLE_IOS(7_0);  


    将任务切换到后台之后,Session的Delegate不会再收到和Task相关的消息。当所有Task全都完成后,程序将被唤醒,并调用ApplicationDelegate的application:handleEventsForBackgroundURLSession:completionHandler:回调,在这里要为后台session(由background session的identifier标识)指定对应的回调代码块。

    随后,对于每一个完成的后台Task调用该Session的Delegate中的URLSession:downloadTask:didFinishDownloadingToURL:(成功的话)和URLSession:task:didCompleteWithError:(成功或者失败都会调用)方法做处理,以上的回调代码块可以在这里调用。

    NSURLSession学习笔记(二)Session Task

    Session Task分为三种Data Task,Upload Task,Download Task。毫无疑问,Session Task是整个NSURLSession架构的核心目标。

    下面写了一个简单的Demo来初步使用下三种任务对象。这里使用的是convenience methods,并没有定制session和使用协议,都是采用completionHandler作为回调动作。


    故事板内容为:

    第一种Data Task用于加载数据,使用全局的shared session和dataTaskWithRequest:completionHandler:方法创建。代码如下:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 使用NSURLSessionDataTask加载网页数据 */  
    2. - (IBAction)loadData:(id)sender {  
    3.     // 开始加载数据,让spinner转起来  
    4.     [self.spinner startAnimating];  
    5.       
    6.     // 创建Data Task,用于打开我的csdn blog主页  
    7.     NSURL *url = [NSURL URLWithString:@"http://blog.csdn.net/u010962810"];  
    8.     NSURLRequest *request = [NSURLRequest requestWithURL:url];  
    9.     NSURLSession *session = [NSURLSession sharedSession];  
    10.     NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request  
    11.                                                 completionHandler:  
    12.                                       ^(NSData *data, NSURLResponse *response, NSError *error) {  
    13.                                           // 输出返回的状态码,请求成功的话为200  
    14.                                           [self showResponseCode:response];  
    15.                                             
    16.                                           // 在webView中加载数据  
    17.                                           [self.webView loadData:data  
    18.                                                         MIMEType:@"text/html"  
    19.                                                 textEncodingName:@"utf-8"  
    20.                                                          baseURL:nil];  
    21.                                             
    22.                                           // 加载数据完毕,停止spinner  
    23.                                           [self.spinner stopAnimating];  
    24.                                       }];  
    25.     // 使用resume方法启动任务  
    26.     [dataTask resume];  
    27. }  
    28.   
    29. /* 输出http响应的状态码 */  
    30. - (void)showResponseCode:(NSURLResponse *)response {  
    31.     NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;  
    32.     NSInteger responseStatusCode = [httpResponse statusCode];  
    33.     NSLog(@"%d", responseStatusCode);  
    34. }  

    completionHandler指定任务完成后的动作。注意一定要使用resume方法启动任务。(Upload Task和Download Task同理)

    运行结果:

    第二种Upload Task用于完成上传文件任务,使用方法类似:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 使用NSURLSessionUploadTask上传文件 */  
    2. - (IBAction)uploadFile:(id)sender {  
    3. //    NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];  
    4. //    NSURLRequest *request = [NSURLRequest requestWithURL:URL];  
    5. //    NSData *data = ...;  
    6. //      
    7. //    NSURLSession *session = [NSURLSession sharedSession];  
    8. //    NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request  
    9. //                                                               fromData:data  
    10. //                                                      completionHandler:  
    11. //                                          ^(NSData *data, NSURLResponse *response, NSError *error) {  
    12. //                                              // ...  
    13. //                                          }];  
    14. //      
    15. //    [uploadTask resume];  
    16. }  



    第三种Download Task用于完成下载文件的任务,使用全局的shared session和downloadTaskWithRequest:completionHandler:方法创建。

    注意:在下载任务完成后,下载的文件位于tmp目录下,由代码块中的location指定(不妨输出看看),我们必须要在completion handler中将文件放到持久化的目录下保存。代码如下:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 使用NSURLSessionDownloadTask下载文件 */  
    2. - (IBAction)downloadFile:(id)sender {  
    3.     [self.spinner startAnimating];  
    4.       
    5.     NSURL *URL = [NSURL URLWithString:@"http://b.hiphotos.baidu.com/image/w%3D2048/sign=6be5fc5f718da9774e2f812b8469f919/8b13632762d0f703b0faaab00afa513d2697c515.jpg"];  
    6.     NSURLRequest *request = [NSURLRequest requestWithURL:URL];  
    7.     NSURLSession *session = [NSURLSession sharedSession];  
    8.     NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request  
    9.                                                             completionHandler:  
    10.                                               ^(NSURL *location, NSURLResponse *response, NSError *error) {  
    11.                                                   [self showResponseCode:response];  
    12.                                                     
    13.                                                   // 输出下载文件原来的存放目录  
    14.                                                   NSLog(@"%@", location);  
    15.                                                     
    16.                                                   // 设置文件的存放目标路径  
    17.                                                   NSString *documentsPath = [self getDocumentsPath];  
    18.                                                   NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath];  
    19.                                                   NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]];  
    20.                                                     
    21.                                                   // 如果该路径下文件已经存在,就要先将其移除,在移动文件  
    22.                                                   NSFileManager *fileManager = [NSFileManager defaultManager];  
    23.                                                   if ([fileManager fileExistsAtPath:[fileURL path] isDirectory:NULL]) {  
    24.                                                       [fileManager removeItemAtURL:fileURL error:NULL];  
    25.                                                   }  
    26.                                                   [fileManager moveItemAtURL:location toURL:fileURL error:NULL];  
    27.                                                     
    28.                                                   // 在webView中加载图片文件  
    29.                                                   NSURLRequest *showImage_request = [NSURLRequest requestWithURL:fileURL];  
    30.                                                   [self.webView loadRequest:showImage_request];  
    31.                                                     
    32.                                                   [self.spinner stopAnimating];  
    33.                                               }];  
    34.       
    35.     [downloadTask resume];  
    36. }  
    37.   
    38. /* 获取Documents文件夹的路径 */  
    39. - (NSString *)getDocumentsPath {  
    40.     NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  
    41.     NSString *documentsPath = documents[0];  
    42.     return documentsPath;  
    43. }  


    运行结果:

    这个Demo中没有为NSURLSession指定session的delegate,所以没有使用委托中的方法,功能比较有限,而且也没有自行定制session的配置,所以只能执行简单的任务,但是对于加载数据,下载一张图片等任务已经可以应付自如。对于创建后台下载任务,支持断点续传的下载任务等将在下一篇文章中分析介绍。

    NSURLSession学习笔记(三)Download Task

    NSURLSession的Download Task用于完成下载任务,本文介绍如何创建断点续传的下载任务和后台下载任务。

    我们直接从分析Demo入手:

    故事板如下:

    只有一个View Controller,用于创建各种下载任务,并将下载后的图片显示到视图上,下载过程中会更新下载进度。

    头文件代码如下:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #import <UIKit/UIKit.h>  
    2.   
    3. @interface ViewController : UIViewController <NSURLSessionDownloadDelegate>  
    4.   
    5. /* NSURLSessions */  
    6. @property (strong, nonatomic)           NSURLSession *currentSession;    // 当前会话  
    7. @property (strong, nonatomic, readonly) NSURLSession *backgroundSession; // 后台会话  
    8.   
    9. /* 下载任务 */  
    10. @property (strong, nonatomic) NSURLSessionDownloadTask *cancellableTask; // 可取消的下载任务  
    11. @property (strong, nonatomic) NSURLSessionDownloadTask *resumableTask;   // 可恢复的下载任务  
    12. @property (strong, nonatomic) NSURLSessionDownloadTask *backgroundTask;  // 后台的下载任务  
    13.   
    14. /* 用于可恢复的下载任务的数据 */  
    15. @property (strong, nonatomic) NSData *partialData;  
    16.   
    17. /* 显示已经下载的图片 */  
    18. @property (weak, nonatomic) IBOutlet UIImageView *downloadedImageView;  
    19.   
    20. /* 下载进度 */  
    21. @property (weak, nonatomic) IBOutlet UILabel *currentProgress_label;  
    22. @property (weak, nonatomic) IBOutlet UIProgressView *downloadingProgressView;  
    23.   
    24. /* 工具栏上的按钮 */  
    25. @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancellableDownload_barButtonItem;  
    26. @property (weak, nonatomic) IBOutlet UIBarButtonItem *resumableDownload_barButtonItem;  
    27. @property (weak, nonatomic) IBOutlet UIBarButtonItem *backgroundDownload_barButtonItem;  
    28. @property (weak, nonatomic) IBOutlet UIBarButtonItem *cancelTask_barButtonItem;  
    29.   
    30. - (IBAction)cancellableDownload:(id)sender; // 创建可取消的下载任务  
    31. - (IBAction)resumableDownload:(id)sender;   // 创建可恢复的下载任务  
    32. - (IBAction)backgroundDownload:(id)sender;  // 创建后台下载任务  
    33. - (IBAction)cancelDownloadTask:(id)sender;  // 取消所有下载任务  
    34.   
    35. @end  



    一、创建普通的下载任务

    这种下载任务是可以取消的,代码如下:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. - (IBAction)cancellableDownload:(id)sender {  
    2.     if (!self.cancellableTask) {  
    3.         if (!self.currentSession) {  
    4.             [self createCurrentSession];  
    5.         }  
    6.           
    7.         NSString *imageURLStr = @"http://farm6.staticflickr.com/5505/9824098016_0e28a047c2_b_d.jpg";  
    8.         NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];  
    9.         self.cancellableTask = [self.currentSession downloadTaskWithRequest:request];  
    10.           
    11.         [self setDownloadButtonsWithEnabled:NO];  
    12.         self.downloadedImageView.image = nil;  
    13.           
    14.         [self.cancellableTask resume];  
    15.     }  
    16. }  


    如果当前的session为空,首先需要创建一个session(该session使用默认配置模式,其delegate为自己):

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 创建当前的session */  
    2. - (void)createCurrentSession {  
    3.     NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];  
    4.     self.currentSession = [NSURLSession sessionWithConfiguration:defaultConfig delegate:self delegateQueue:nil];  
    5.     self.currentSession.sessionDescription = kCurrentSession;  
    6. }  

    随后创建下载任务并启动。

    这种任务是可取消的,即下次下载又从0.0%开始:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. if (self.cancellableTask) {  
    2.     [self.cancellableTask cancel];  
    3.     self.cancellableTask = nil;  
    4. }  



    二、创建可恢复的下载任务

    可恢复的下载任务支持断点续传,也就是如果暂停当前任务,在下次再执行任务时,将从之前的下载进度中继续进行。因此我们首先需要一个NSData对象来保存已经下载的数据:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 用于可恢复的下载任务的数据 */  
    2. @property (strong, nonatomic) NSData *partialData;  


    执行下载任务时,如果是恢复下载,那么就使用downloadTaskWithResumeData:方法根据partialData继续下载。代码如下:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. - (IBAction)resumableDownload:(id)sender {  
    2.     if (!self.resumableTask) {  
    3.         if (!self.currentSession) {  
    4.             [self createCurrentSession];  
    5.         }  
    6.           
    7.         if (self.partialData) { // 如果是之前被暂停的任务,就从已经保存的数据恢复下载  
    8.             self.resumableTask = [self.currentSession downloadTaskWithResumeData:self.partialData];  
    9.         }  
    10.         else { // 否则创建下载任务  
    11.             NSString *imageURLStr = @"http://farm3.staticflickr.com/2846/9823925914_78cd653ac9_b_d.jpg";  
    12.             NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];  
    13.             self.resumableTask = [self.currentSession downloadTaskWithRequest:request];  
    14.         }  
    15.           
    16.         [self setDownloadButtonsWithEnabled:NO];  
    17.         self.downloadedImageView.image = nil;  
    18.           
    19.         [self.resumableTask resume];  
    20.     }  
    21. }  


    在取消下载任务时,要将partialData数据保存起来,而且不要调用cancel方法:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. else if (self.resumableTask) {  
    2.     [self.resumableTask cancelByProducingResumeData:^(NSData *resumeData) {  
    3.         // 如果是可恢复的下载任务,应该先将数据保存到partialData中,注意在这里不要调用cancel方法  
    4.         self.partialData = resumeData;  
    5.         self.resumableTask = nil;  
    6.     }];  
    7. }  


    另外在恢复下载时,NSURLSessionDownloadDelegate中的以下方法将被调用:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 从fileOffset位移处恢复下载任务 */  
    2. - (void)URLSession:(NSURLSession *)session  
    3.       downloadTask:(NSURLSessionDownloadTask *)downloadTask  
    4.  didResumeAtOffset:(int64_t)fileOffset  
    5. expectedTotalBytes:(int64_t)expectedTotalBytes {  
    6.     NSLog(@"NSURLSessionDownloadDelegate: Resume download at %lld", fileOffset);  
    7. }  



    三、创建后台下载任务

    后台下载任务,顾名思义,当程序进入后台后,下载任务依然继续执行。

    首先创建一个后台session单例,这里的Session配置使用后台配置模式,使用backgroundSessinConfiguration:方法配置时应该通过后面的参数为该后台进程指定一个标识符,在有多个后台下载任务时这个标识符就起作用了。

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 创建一个后台session单例 */  
    2. - (NSURLSession *)backgroundSession {  
    3.     static NSURLSession *backgroundSess = nil;  
    4.     static dispatch_once_t onceToken;  
    5.     dispatch_once(&onceToken, ^{  
    6.         NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:kBackgroundSessionID];  
    7.         backgroundSess = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];  
    8.         backgroundSess.sessionDescription = kBackgroundSession;  
    9.     });  
    10.       
    11.     return backgroundSess;  
    12. }  


    在创建后台下载任务时,应该使用后台session创建,然后resume。

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. - (IBAction)backgroundDownload:(id)sender {  
    2.     NSString *imageURLStr = @"http://farm3.staticflickr.com/2831/9823890176_82b4165653_b_d.jpg";  
    3.     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];  
    4.     self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request];  
    5.       
    6.     [self setDownloadButtonsWithEnabled:NO];  
    7.     self.downloadedImageView.image = nil;  
    8.       
    9.     [self.backgroundTask resume];  
    10. }  


    在程序进入后台后,如果下载任务完成,程序委托中的对应方法将被回调:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 后台下载任务完成后,程序被唤醒,该方法将被调用 */  
    2. - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {  
    3.     NSLog(@"Application Delegate: Background download task finished");  
    4.       
    5.     // 设置回调的完成代码块  
    6.     self.backgroundURLSessionCompletionHandler = completionHandler;  
    7. }  


    然后调用NSURLSessionDownloadDelegate中的方法:

    以下是

    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL*)location中的方法,该方法只有下载成功才被调用:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. else if (session == self.backgroundSession) {  
    2.     self.backgroundTask = nil;  
    3.     AppDelegate *appDelegate = [AppDelegate sharedDelegate];  
    4.     if (appDelegate.backgroundURLSessionCompletionHandler) {  
    5.         // 执行回调代码块  
    6.         void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;  
    7.         appDelegate.backgroundURLSessionCompletionHandler = nil;  
    8.         handler();  
    9.     }  
    10. }  


    另外无论下载成功与否,以下方法都会被调用:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 完成下载任务,无论下载成功还是失败都调用该方法 */  
    2. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {  
    3.     NSLog(@"NSURLSessionDownloadDelegate: Complete task");  
    4.       
    5.     dispatch_async(dispatch_get_main_queue(), ^{  
    6.         [self setDownloadButtonsWithEnabled:YES];  
    7.     });  
    8.       
    9.     if (error) {  
    10.         NSLog(@"下载失败:%@", error);  
    11.         [self setDownloadProgress:0.0];  
    12.         self.downloadedImageView.image = nil;  
    13.     }  
    14. }  


    取消后台下载任务时直接cancel即可:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. else if (self.backgroundTask) {  
    2.     [self.backgroundTask cancel];  
    3.     self.backgroundTask = nil;  
    4. }  



    四、NSURLSessionDownloadDelegate

    为了实现下载进度的显示,需要在委托中的以下方法中实现:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* 执行下载任务时有数据写入 */  
    2. - (void)URLSession:(NSURLSession *)session  
    3.       downloadTask:(NSURLSessionDownloadTask *)downloadTask  
    4.       didWriteData:(int64_t)bytesWritten // 每次写入的data字节数  
    5.  totalBytesWritten:(int64_t)totalBytesWritten // 当前一共写入的data字节数  
    6. totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 期望收到的所有data字节数  
    7. {  
    8.     // 计算当前下载进度并更新视图  
    9.     double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;  
    10.     [self setDownloadProgress:downloadProgress];  
    11. }  
    12.   
    13. /* 根据下载进度更新视图 */  
    14. - (void)setDownloadProgress:(double)progress {  
    15.     NSString *progressStr = [NSString stringWithFormat:@"%.1f", progress * 100];  
    16.     progressStr = [progressStr stringByAppendingString:@"%"];  
    17.       
    18.     dispatch_async(dispatch_get_main_queue(), ^{  
    19.         self.downloadingProgressView.progress = progress;  
    20.         self.currentProgress_label.text = progressStr;  
    21.     });  
    22. }  



    从已经保存的数据中恢复下载任务的委托方法,fileOffset指定了恢复下载时的文件位移字节数:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* Sent when a download has been resumed. If a download failed with an 
    2.  * error, the -userInfo dictionary of the error will contain an 
    3.  * NSURLSessionDownloadTaskResumeData key, whose value is the resume 
    4.  * data.  
    5.  */  
    6. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask  
    7.                                       didResumeAtOffset:(int64_t)fileOffset  
    8.                                      expectedTotalBytes:(int64_t)expectedTotalBytes;  



    只有下载成功才调用的委托方法,在该方法中应该将下载成功后的文件移动到我们想要的目标路径:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* Sent when a download task that has completed a download.  The delegate should  
    2.  * copy or move the file at the given location to a new location as it will be  
    3.  * removed when the delegate message returns. URLSession:task:didCompleteWithError: will 
    4.  * still be called. 
    5.  */  
    6. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask  
    7.                               didFinishDownloadingToURL:(NSURL *)location;  



    无论下载成功或失败都会调用的方法,类似于try-catch-finally中的finally语句块的执行。如果下载成功,那么error参数的值为nil,否则下载失败,可以通过该参数查看出错信息:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /* Sent as the last message related to a specific task.  Error may be 
    2.  * nil, which implies that no error occurred and this task is complete.  
    3.  */  
    4. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task  
    5.                            didCompleteWithError:(NSError *)error;  



    后台下载的运行结果:

    启动任务后,进入后台:


    下载完成后,控制台将会“通知”我们:

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. 2014-02-05 18:30:39.767 DownloadTask[3472:70b] Application Delegate: App did become active  
    2. 2014-02-05 18:30:43.734 DownloadTask[3472:70b] Application Delegate: App will resign active  
    3. 2014-02-05 18:30:43.735 DownloadTask[3472:70b] Application Delegate: App did enter background  
    4. 2014-02-05 18:30:45.282 DownloadTask[3472:70b] Application Delegate: Background download task finished  
    5. 2014-02-05 18:30:45.285 DownloadTask[3472:4907] NSURLSessionDownloadDelegate: Finish downloading  
    6. 2014-02-05 18:30:45.301 DownloadTask[3472:4907] NSURLSessionDownloadDelegate: Complete task  


    再次启动程序,可以看到加载好的页面:


    可以看到,通过后台下载让我们的程序更加异步地运行。NSURLSession封装了对应的接口,让我们要执行的任务更加专门化,这个新的网络架构的功能真的很强大。

    本文的Demo基于https://github.com/ShinobiControls/iOS7-day-by-day改写,内容基本一致。

    原来的Demo也有一篇博客对应:iOS7 Day-by-Day :: Day 1 :: NSURLSession

    本文的Demo也已经上传,有兴趣的话可以下载看看。

  • 相关阅读:
    进程池,线程池,协程,gevent模块,协程实现单线程服务端与多线程客户端通信,IO模型
    线程相关 GIL queue event 死锁与递归锁 信号量l
    生产者消费者模型 线程相关
    进程的开启方式 进程的join方法 进程间的内存隔离 其他相关方法 守护进程 互斥锁
    udp协议 及相关 利用tcp上传文件 socketserver服务
    socket套接字 tcp协议下的粘包处理
    常用模块的完善 random shutil shevle 三流 logging
    day 29 元类
    Django入门
    MySQL多表查询
  • 原文地址:https://www.cnblogs.com/On1Key/p/5440198.html
Copyright © 2011-2022 走看看