简单的代理类的接口
下面的代码片段基于清单1-1所示接口
清单1-1
#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
创建并配置一个会话
NSURLSession提供了大量的配置选项:
- 支持对缓存,cookies,证书的私有存储,以及对单例会话的特定协议
- 关联到一个特定请求(任务),或者一组请求(会话)的认证
- 通过URL上传或下载文件,支持将元数据分割成基于文件内容的短数据
- 配置每个主机的最大连接数
- 当资源无法在一个确定时间内下载时,配置一个超时时间
- 支持安全传输层协议(TLS)的版本区间
- 自定义代理
- cookie的管理策略
- HTTP传输管理
大部分的配置都在一个configuration对象中设置,可以通用一些基本设置.初始化一个会话对象(session object)需要指定如下信息:
- 一个configuration对象用来管理会话或任务的行为
- 可选的,一个代理对象用来表示接收数据的进度,会话任务或会话其他事件的进度,比如服务器认证,决定一个加载请求是否可转换为下载请求,等等
如果没有指定一个代理,NSURLSession对象将使用系统提供得代理.在这种方式中,你可以轻松的使用NSURLSession替代已存在的sendAsynchronousRequest:queue:completionHandler:方法.
注意:如果app需要在后台进行数据传输,必须使用自定义代理.
创建一个会话对象之后,不能再去修改它的configuration对象和代理,除了重新创建一个会话.
清单1-2展示了创建默认会话,临时会话和后台会话的示例代码
#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]];
除了后台配置对象(background configurations),可以重用配置对象来创建其他的会话.(不能重用后台配置对象是因为两个后台会话不能使用相同的标识符identifier)
你可以在任何时间安全的修改一个configuration对象.因为当创建一个会话时,configuration对象的传递是由深拷贝实现的,所以修改只会影响之后创建的会话,不会对已存在的会话造成影响.例如,你可能想创建另一个只有在WiFi环境下才能重连数据的会话,如1-3中所示:
清单1-3重用configuration对象
ephemeralConfigObject.allowsCellularAccess = YES;
// ...
NSURLSession *ephemeralSessionWiFiOnly = [NSURLSession sessionWithConfiguration: ephemeralConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
使用系统提供的代理抓取资源
最简单直接的使用NSURLSession的方法是用来替换掉之前的sendAsynchronousRequest:queue:completionHandler:方法.要这么做,你需要在app中实现两处代码:
- 创建configuration对象,以及一个基于该configuration对象的会话对象
- 一个完成处理程序来处理数据接收完成后要做的事情
使用系统提供的代理,你可以每个请求只用一行代码来抓取特定URL.清单1-4示例了最简单的实现.
注意:系统提供的代理仅仅实现了有限网络功能.如果app的需求超出了基本的URL加载,比如自定义认证或者数据后台下载,那么需要实现一个完整的代理,参见URL Session的生命周期.
清单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:提供了任务请求返回的数据,周期性的返回数据块
- URLSession:task:didCompleteWithError:表明数据是否全部完成接收
如果app需要在URLSession:dataTask:didReceiveData:方法返回之后使用数据,必须用代码实现数据存储.
例如,一个web浏览器可能需要根据之前接收的数据来渲染当前接收的数据.要实现这个功能可以使用一个NSMutableData对象来存储结果数据,然后使用 appendData: 来将当前接收的数据拼接到之前接收到的数据中.
清单1-5示例了如何创建开始一个数据任务:
NSURL *url = [NSURL URLWithString: @"http://www.example.com/"];
NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithURL: url];
[dataTask resume];
下载文件
某种程序上,下载文件和接收数据类似.app应当实现以下的代理方法:
- URLSession:downloadTask:didFinishDownloadingToURL:提供app下载内容的临时存储目录. 注意:在这个方法返回之前,必须打开文件来进行读取或者将下载内容移动到一个永久目录.当方法返回后,临时文件将会被删除.
- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 提供了下载进度的状态信息.
- URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 告诉app尝试恢复之前失败的下载.
- URLSession:task:didCompleteWithError:告诉app下载失败
如果将下载任务安排在后台会话中,在app非运行期间下载行为仍将继续.如果将下载任务安排在系统默认会话或者临时会话中,当app重新启动时,下载也将重新开始.
在跟服务器传输数据期间,如果用户进行了暂停操作,app可以调用cancelByProducingResumeData: 方法取消任务.然后,app可以将已传输的数据作为参数传递给downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:来创建一个新的下载任务继续下载.
清单1-6示例了一个大文件的下载.清单1-7示例了下载任务的代理方法.
清单1-6 下载任务示例:
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];
清单1-7 下载任务的代理方法:
-(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);
}
上传数据内容
app能通过三种方式通过提供HTTP POST请求体:NSData对象,文件和流.通常情况下,app应当:
- 使用一个NSData对象,如果内存中已经存在相应数据,并且数据不会被无理由的销毁.
- 使用文件形式,如果要上传的内容是通过文件形式存储在硬盘中的,或者将要上传的内容写入文件,要是这样可以解决内存的话.
- 使用流,如果是从网络接收数据,或者转化已存在的提供了流的NSURLConnection代码.
无论你选择了哪种方法,如果app提供了自定义代理,都应该实现URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: 方法来获取上传进度.
此外,如果app使用流作为请求体,还必须提供一个自定义会话代理实现URLSession:task:needNewBodyStream:方法,详细描述在通过流上传数据
使用NSData对象上传
使用NSData对象上传数据,app需要调用uploadTaskWithRequest:fromData:或者uploadTaskWithRequest:fromData:completionHandler:来创建一个上传任务,将要上传的NSData对象传递给fromData参数.
会话对象根据NSData对象计算内容长度,赋值给请求头的Content-Length.app还要在URL request对象中提供服务器可能需要的请求头信息-例如:content type.
使用文件形式上传
使用文件形式上传,app需要调用 uploadTaskWithRequest:fromFile:或者uploadTaskWithRequest:fromFile:completionHandler: 方法来创建一个上传任务,以及一个文件路径来读取内容.
会话对象自动计算Content-Length,如果app没有提供 Content-Type,会话对象将自动生成一个.app还要在URL request对象中提供服务器可能需要的请求头信息
使用流形式上传
使用流来上传信息,app需要调用 uploadTaskWithStreamedRequest: 方法来创建一个上传任务.app提供一个绑定了流的request对象.app还要在URL request对象中提供服务器可能需要的请求头信息,比如content-type和content-length.
此外,因为会话对象不能保证必定能从提供的流中读取数据,所以app需要提供一个新的流以便会话重新进行请求(比如,认证失败).app需要实现 URLSession:task:needNewBodyStream:方法.当这个方法被调用时,app需要取得或者创建一个新的流,然后调用提供的完成处理块.
注意:因为app必须实现URLSession:task:needNewBodyStream:方法,所以这种形式不支持使用系统默认的代理.
使用下载任务来上传文件
当下载任务创建时,app需要提供一个NSData对象或者一个流作为NSURLRequest对象的参数.
如果使用数据流,app需要实现 URLSession:task:needNewBodyStream: 方法来处理认证失败的情况.详细描述在通过流上传数据
处理认证和安全传输确认
如果远程服务器返回一个状态值表明需要进行认证或者认证需要特定的环境(例如一个SSL客户端证书),NSURLSession调用会调用一个认证相关的代理方法.
- 会话级别:NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate,或者 NSURLAuthenticationMethodServerTrust,会话对象调用会话代理方法URLSession:didReceiveChallenge:completionHandler: .如果app没有提供会话代理,会话对象调用任务得代理方法URLSession:task:didReceiveChallenge:completionHandler:.
- 非会话级别:NSURLSession对象调用会话代理方法URLSession:task:didReceiveChallenge:completionHandler:.如果app提供了会话代理,而且app需要处理认证,那么你必须在任务级别进行处理. 在非会话级别上,URLSession:didReceiveChallenge:completionHandler:不会被调用.
更多信息参见Authentication Challenges and TLS Chain Validation.
处理iOS后台活动
在iOS中使用NSURLSession,当一个下载任务完成时,app将会自动重启.app代理方法application:handleEventsForBackgroundURLSession:completionHandler: 负责重建合适的会话,存储完成处理器,并在会话对象调用会话代理的URLSessionDidFinishEventsForBackgroundURLSession: 方法时调用完成处理器.
清单1-8,清单1-9分别示例了这些会话和app代理方法
清单1-8,iOS后台下载的会话代理方法
#if TARGET_OS_IPHONE
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
NSLog(@"Background URL session %@ finished events.\n", session);
if (session.configuration.identifier)
[self callCompletionHandlerForSession: session.configuration.identifier];
}
- (void) addCompletionHandler: (CompletionHandlerType) handler forSession: (NSString *)identifier
{
if ([ self.completionHandlerDictionary objectForKey: identifier]) {
NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n");
}
[ self.completionHandlerDictionary setObject:handler forKey: identifier];
}
- (void) callCompletionHandlerForSession: (NSString *)identifier
{
CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier];
if (handler) {
[self.completionHandlerDictionary removeObjectForKey: identifier];
NSLog(@"Calling completion handler.\n");
handler();
}
}
#endif
清单1-9,iOS后台下载的app代理方法
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration: identifier];
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate: self.mySessionDelegate delegateQueue: [NSOperationQueue mainQueue]];
NSLog(@"Rejoining session %@\n", identifier);
[ self.mySessionDelegate addCompletionHandler: completionHandler forSession: identifier];
}
转载请注明出处:http://blog.csdn.net/qq329735967
任何疑问欢迎Email至[email protected]