URL Loading System Programming Guide-使用NSURLSession

使用NSURLSession

NSURLSession及其相关的类提供了通过HTTP来下载内容的API。这些API提供了丰富的代理方法来支持授权,以及在app没有运行,或者在iOS中,app被挂起时,来执行后台下载。
使用NSURLSession API,需要创建一系列的session,每个session协调一组相关的数据传送任务。例如,如果你正在写一个浏览器,你可能会为每个tab或者window创建一个session。
与大多数网络APIs类似,NSURLSession API也是高度异步的。如果你使用默认的,系统提供的代理方法,当传输成功或者出错时,必须提供一个完成处理的回调block。另一种情况是,你提供了自定义的代理对象,当从服务器接收到数据时(对于文件下载,文件完成传送),任务对象会调用这些代理方法。

注意:完成的回调方法主要是用来替代自定义的代理的。如果你创建的任务使用了回调,那么响应的代理和数据传输的代理方法,将不会调用

NSURLSession API提供了状态和进度属性,并把这些信息传递给代理。它支持取消、 重新启动 (恢复),和挂起任务,并提供了能够恢复暂停、 取消,或者失败的下载的能力。

理解URL Session概念

session中任务的行为取决于三件东西:session的类型(由创建它的配置对象决定)、task的类型,和当创建task时,app是否在前台。

sessions 的类型

NSURLSessionAPI支持3种类型的session,是由创建session时用到的配置对象决定的:

  • Default sessions 与其他的下载URLs的Foundation方法相似。它使用持久性的基于磁盘的缓存,并将凭据保存在用户的钥匙串中。
  • Ephemeral sessions不存储任何数据到磁盘,所有缓存、 凭据存储等等都是保存在 RAM 并且和session绑定。因此,当您的应用程序作废session时,它们被自动清除。
  • Background sessions类似于默认会话,除了有一个单独的进程处理所有的数据传输。Background sessions有一些额外的限制,在Background Transfer Considerations 中描述。

Tasks的类型

在一个session中,NSURLSession 支持三种类型的tasks:data tasks,download tasks,和 upload tasks。

  • Data tasks使用 NSData 对象发送和接收数据。Data tasks是为短的,通常是从您的应用程序到服务器的交互式请求而准备的。Data tasks可以在接收到每个数据片段后返回数据,也可以一次全部完成后通过一个完成后的handler来返回数据。因为data task不将数据存储到一个文件,它们不支持Background sessions。
  • Download tasks以文件的形式检索数据,并支持后台下载当应用程序未运行的时候。
  • Upload tasks发送数据 (通常以文件的形式),并支持后台上传当应用程序未运行的时候。

后台传输注意事项

NSURLSession 支持后台传输当app被挂起的时候。后台传输仅仅由使用background session配置对象创建的sessions来提供。(调用 backgroundSessionConfiguration 返回)。

background session由于实际传送是由一个单独的进程来执行的,而且由于重新启动您的应用程序的过程是相对昂贵的,所以导致一些功能将不可用,导致以下的限制:

  • session必须为事件交付提供一个代理。(对于上传和下载,代理的行为和处理传送相同。)
  • 只支持HTTP和HTTPS协议。(不支持自定义协议)
  • 只支持upload和download task。(不支持data task)
  • 总是有重定向。
  • 如果app在后台时初始化后台传送,配置对象的discretionary属性被视为ture。

app重新加载的方式在iOS和OS X中有轻微的不同。
在iOS中,当后台传输完成或者需要credential的时候,如果你的app不再运行,iOS会自动在后台加载app,并调用app的 UIApplicationDelegate 对象的 application:handleEventsForBackgroundURLSession:completionHandler: 方法。这个调用方法提供了加载你app的session的标示符。你的app应该保存这个完成的handler,使用这个session的标示符创建一个配置对象,使用这个配置对象创建一个session。这个新的session会自动和正在后台运行的活动联系在一起。然后,当session完成了后台的下载任务,它给session的代理发送URLSessionDidFinishEventsForBackgroundURLSession: 消息。然后,你的session的代理应该调用保存的那个completion handler。

在iOS和OS X中,当用户重新加载你的app时,app应该立即使用上次app运行时未完成的任务session的标示符创建配置对象,然后为每个配置对象创建一个session。这个新的session会自动和正在后台运行的活动联系在一起。

注意:你必须为每个标示符创建唯一的一个session (特别是你创建配置对象的时候) 。多个session共享相同的标识符的行为是未定义的。

当app被挂起的时候,任何任务完成都会调用URLSession:downloadTask:didFinishDownloadingToURL: 方法,并会带上task和与之相关的新下载文件的URL。

类似的,如果task需要credentials,NSURLSession 对象会调用代理的 URLSession:task:didReceiveChallenge:completionHandler: 或者 URLSession:didReceiveChallenge:completionHandler: 方法。

怎样使用NSURLSession 后台传输的例子,参见Simple Background Transfer.

生命周期和代理交互

取决于你如何运用NSURLSession 类,了解session的生命周期可以会很有帮助,这包括session是如何和代理相互作用的,哪个代理被调用,当服务器返回一个重定向时会发生什么,当你的app回复一个失败的下载时会发生什么,等等。

URL session生命周期的完整描述,请阅读 Life Cycle of a URL Session.

NSCopying的行为

Session和task对象都遵守 NSCopying 协议,如下:

  • 当app复制一个session或者task对象时,会返回同一个对象。
  • 当app复制一个配置对象时,会返回一个新的对象,这样的话你能独立的修改。

代理类接口的例子

下面的task部分的代码片段基于Listing 1-1 所示的类接口。

Listing 1-1  Sample delegate class interface
#import <Foundation/Foundation.h>


typedef void (^CompletionHandlerType)();

@interface MySessionDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>

@property NSURLSession *backgroundSession;
@property NSURLSession *defaultSession;
@property NSURLSession *ephemeralSession;

#if TARGET_OS_IPHONE
@property NSMutableDictionary *completionHandlerDictionary;
#endif

- (void) addCompletionHandler: (CompletionHandlerType) handler forSession: (NSString *)identifier;
- (void) callCompletionHandlerForSession: (NSString *)identifier;


@end

创建和配置Session

由于大多数的设置都被包含在一个单独的配置对象中,你能重用这些设置。在实例化一个session对象时,需指定如下的东西:

  • 一个管理session的行为和其中的任务的配置对象
  • 可选,一个委托对象以处理传入的数据,和处理特定的session以及其中的任务的事件,如服务器身份验证,确定一个资源任务加载请求是否应该转换成一个下载,等等

如果不提供代理对象,NSURLSession 会使用系统提供的代理对象。这样的话,你可以使用NSURLSession的sendAsynchronousRequest:queue:completionHandler:方法取代现有的方法。

注意:如果你的app需要执行后台的传输,必须提供一个自定义的代理。

在实例化一个session对象后,没有创建一个新的session,你不能改变配置or代理对象。

下面的例子,Listing 1-2展示了如何创建normal, ephemeral, and background sessions.

Listing 1-2 Creating and configuring sessions

#if TARGET_OS_IPHONE
self.completionHandlerDictionary = [NSMutableDictionary dictionaryWithCapacity:0];
#endif

/* Create some configuration objects. */

NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration: @"myBackgroundSessionIdentifier"];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSessionConfiguration *ephemeralConfigObject = [NSURLSessionConfiguration ephemeralSessionConfiguration];


/* Configure caching behavior for the default session.
   Note that iOS requires the cache path to be a path relative
   to the ~/Library/Caches directory, but OS X expects an
   absolute path.
 */
#if TARGET_OS_IPHONE
NSString *cachePath = @"/MyCacheDirectory";

NSArray *myPathList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *myPath    = [myPathList  objectAtIndex:0];

NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];

NSString *fullCachePath = [[myPath stringByAppendingPathComponent:bundleIdentifier] stringByAppendingPathComponent:cachePath];
NSLog(@"Cache path: %@\n", fullCachePath);
#else
NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"/nsurlsessiondemo.cache"];

NSLog(@"Cache path: %@\n", cachePath);
#endif





NSURLCache *myCache = [[NSURLCache alloc] initWithMemoryCapacity: 16384 diskCapacity: 268435456 diskPath: cachePath];
defaultConfigObject.URLCache = myCache;
defaultConfigObject.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;

/* Create a session for each configurations. */
self.defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
self.backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
self.ephemeralSession = [NSURLSession sessionWithConfiguration: ephemeralConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];

除了后台配置,您可以重用session配置对象来创建附加的session。(你不能重用后台session配置,因为共享相同的标识符的两个背景session对象的行为是未定义的。)
你可以安全的在任何时间修改配置对象。当你创建一个session时,session对配置对象执行深复制,所以修改只会影响新的session,不会影响已经存在的session。例如,你可能会为检索的内容创建第二个session,当你在 有Wi-Fi 连接的时候。如List 1-3 所示。
Listing 1-3 Creating a second session with the same configuration object

ephemeralConfigObject.allowsCellularAccess = YES;

// ...

NSURLSession *ephemeralSessionWiFiOnly = [NSURLSession sessionWithConfiguration: ephemeralConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];

使用系统提供的对象来获取数据

使用此种方法,app需提供了两段代码:

  • 创建一个配置对象,和基于此配置对象的session。
  • 一个接收完数据后的completion handler

Listing 1-4 使用系统提供的代理来请求数据

NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
[[delegateFreeSession dataTaskWithURL: [NSURL URLWithString: @"http://www.example.com/"]
                   completionHandler:^(NSData *data, NSURLResponse *response,
                                       NSError *error) {
                       NSLog(@"Got response %@ with error %@.\n", response, error);
                       NSLog(@"DATA:\n%@\nEND DATA\n",
                             [[NSString alloc] initWithData: data
                                     encoding: NSUTF8StringEncoding]);
                   }] resume];

使用自定代理

使用自定义代理,至少需要实现如下的方法:

URLSession:dataTask:didReceiveData:中可能需要使用NSMutableDataappendData:方法来保存数据。

Listing 1-5 展示如何创建和启动一个data task

Listing 1-5 Data task example

 NSURL *url = [NSURL URLWithString: @"http://www.example.com/"];

NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithURL: url];
[dataTask resume];

下载文件

在高层次上,下载文件与检索数据相同。app需实现如下的代理方法:

  • URLSession:downloadTask:didFinishDownloadingToURL: 提供存储下载内容的临时文件的URL。

    Important:在这个方法返回前,它必须打开文件来read或者把它移动到一个永久的位置。当这个方法返回时,如果文件还在原来的位置,它将会被删除。

  • URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:提供下载精度的状态信息。

  • URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 告诉app,尝试恢复以前失败的下载成功。
  • URLSession:task:didCompleteWithError:下载失败

如果在后台session中下载,当app没有运行时这个下载还会继续。如果在一个standard或者ephemeral session中下载,当app重新启动时,下载必须重新开始。
在服务器传输数据的过程中,如果用户暂停下载,app使用cancelByProducingResumeData:方法来取消任务。过后,app把resume的数据传递给downloadTaskWithResumeData:或者 downloadTaskWithResumeData:completionHandler:方法,来创建一个新的下载任务来继续下载。
如果数据传输失败,代理方法的URLSession:task:didCompleteWithError: 将会调用。如果任务是可重新开始的,对象的 userInfo 字典会包含NSURLSessionDownloadTaskResumeData键的值。app应使用reachability APIs 来决定什么时候来再次尝试,然后调用downloadTaskWithResumeData: 或者 downloadTaskWithResumeData:completionHandler: 来创建新的下载任务来继续下载。

List 1-6 提供了一个下载中等大小文件的例子。List1-7 提供了下载任务代理方法的例子。

Listing 1-6 Download task example

  NSURL *url = [NSURL URLWithString: @"https://developer.apple.com/library/ios/documentation/Cocoa/Reference/"
              "Foundation/ObjC_classic/FoundationObjC.pdf"];

NSURLSessionDownloadTask *downloadTask = [self.backgroundSession downloadTaskWithURL: url];
[downloadTask resume];

Listing 1-7 Delegate methods for download tasks

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"Session %@ download task %@ finished downloading to URL %@\n",
        session, downloadTask, location);

#if 0
/* Workaround */
[self callCompletionHandlerForSession:session.configuration.identifier];
#endif

#define READ_THE_FILE 0
#if READ_THE_FILE
/* Open the newly downloaded file for reading. */
NSError *err = nil;
NSFileHandle *fh = [NSFileHandle fileHandleForReadingFromURL:location
    error: &err];

/* Store this file handle somewhere, and read data from it. */
// ...

#else
NSError *err = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *cacheDir = [[NSHomeDirectory()
    stringByAppendingPathComponent:@"Library"]
    stringByAppendingPathComponent:@"Caches"];
NSURL *cacheDirURL = [NSURL fileURLWithPath:cacheDir];
if ([fileManager moveItemAtURL:location
    toURL:cacheDirURL
    error: &err]) {

    /* Store some reference to the new URL */
} else {
    /* Handle the error. */
}
#endif

}

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"Session %@ download task %@ wrote an additional %lld bytes (total %lld bytes) out of an expected %lld bytes.\n",
        session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    NSLog(@"Session %@ download task %@ resumed at offset %lld bytes out of an expected %lld bytes.\n",
        session, downloadTask, fileOffset, expectedTotalBytes);
}

上传Body内容

app可以为HTTP POST请求提供3种方式的body content:一个NSData对象,一个文件,一个stream

  • 使用NSData对象,如果在内存中有NSData对象,并且没有弃置它
  • 使用file,如果你上传的内容已存在在磁盘上或者,把它写到磁盘上有释放内存的好处
  • 使用stream,if you are receiving the data over a network or are converting existing NSURLConnection code that provides the request body as a stream.

不论你使用哪种方式,如果app提供了一个自定义的代理对象,代理对象应实现如下的方法:URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: ,来获取上传进度信息。
另外,如果app使用stream来提供request body,它必须提供一个实现URLSession:task:needNewBodyStream:的自定义session代理,详细信息请参考Uploading Body Content Using a Stream.

使用NSData对象上传Body Content

为了使用NSdata对象,app可以调用uploadTaskWithRequest:fromData:或者uploadTaskWithRequest:fromData:completionHandler:方法来创建一个上传的任务,并通过fromData参数来提供请求体数据。
session对象基于data对象的大小来计算Content-Length的长度。
app也可能需要提供服务器需要提供的其它信息,如content type等。

使用File上传Body Content

app调用uploadTaskWithRequest:fromFile: 或者uploadTaskWithRequest:fromFile:completionHandler: 方法来创建一个上传任务,并提供一个任务用来读取body content的URL路径。

使用Stream上传Body Content

使用uploadTaskWithStreamedRequest: 方法来创建一个上传任务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值