前言
如果问一个iOS开发的同学,你们的网络请求是使用的什么框架呢?不用怀疑,十有八九都会回答使用的是AFNetworking。你也可能自做iOS开发以来,已经无数次的使用AFNetworking中的AFHTTPSessionManager对象或AFURLSessionManager对象发送GET/POST请求,但你真的知道这样一个看似简单的网络请求是怎么实现的吗?
AFNetworking核心---NSURLSession
- NSURLSession对象是什么
NSURLSession is a replacement API for NSURLConnection. It provides options that affect the policy of, and various aspects of the mechanism by which NSURLRequest objects are retrieved from the network.
An NSURLSession may be bound to a delegate object. The delegate is invoked for certain events during the lifetime of a session, such as server authentication or determining whether a resource to be loaded should be converted into a download.
NSURLSession instances are threadsafe.The default NSURLSession uses a system provided delegate and is appropriate to use in place of existing code that uses +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]
An NSURLSession creates NSURLSessionTask objects which represent the action of a resource being loaded. These are analogous to NSURLConnection objects but provide for more control and a unified delegate model.
从NSURLSession.h文件中对NSURLSession对象的描述,我们可以知道如下几条信息
1 NSURLSession对象是NSURLConnection对象的替代api。在iOS7.0之前,不管当时你使用牛逼的ASIHttpRequest网络框架还是使用原生网络请求,都绕不过NSURLConnection。对,从这个时候NSURLSession成为了苹果的亲儿子。NSURLSession对象通过创建时设置配置选项NSURLSessionConfiguration对象,影响NSURLRequest对象的方方面面,如网络超时时间、缓存策略、是否允许使用蜂窝网络用于请求等。
2 当你查看了NSURLSession.h文件,你会发现其中声明了很多代理方法。你肯定会想这些代理方法什么时候会被触发、谁又是这些代理事件的委托者呢?一切都没错!就如An NSURLSession may be bound to a delegate object说的,NSURLSession对象与代理对象有着千丝万缕的联系,在通过NSURLSession对象参与网络请求的故事里,它就是代理事件的委托者。显而易见,所有代理事件的触发都将会在NSURLSession对象的生命周期中调用。
3 NSURLSession是线程安全的,默认的NSURLSession对象使用使用系统提供的代理对象( 当然你也可以为其指定代理对象),在合适的地方替代原有使用NSURLConnection发送网络请求的代码。如同NSURLConnection,我们也可以使用completionHandler回调获取数据,并不需要自己处理代理事件。
4 当你查看了NSURLSession.h文件,你会发现NSURLSessionTask及其子类都可通过NSURLSession提供的类方法创建的,你可以把NSURLSessionTask理解为一个表示某项资源将被加载的任务。
-
NSURLSession对象分类
NSURLSession通过设置不同类别的NSURLSessionConfiguration对象可配置不同类别的NSURLSession对象
+ (NSURLSession *)sessionWithConfiguration: (NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration: (NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;很明显,NSURLSessionConfiguration的类别决定了NSURLSession对象的性质。
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
@property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;
#endif
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);
如果现在你不能理解它们间的差异也没有关系!通过描述或许也能看明白它们间的差异是围绕着数据缓存(caches)、cookies、证书的存储方式不同展开,需要注意backgroundSession是一个后台Session,我们可以通过该类别的session在后台进行数据的下载与上传。
NSURLSession之代理事件
//该协议中的方法处理session层级的事件,具体事件调用会在后续的例子中演示
@protocol NSURLSessionDelegate <NSObject>
@optional
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session NS_AVAILABLE_IOS(7_0);
@end
//该协议中的方法处理task层级的事件,各种类型task的公共事件
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@optional
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error;
@end
//该协议中的方法处理task层级(dataTask与uploadTask)的事件
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@optional
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler;
@end
//该协议中的方法处理task层级(downloadTask)的事件
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location;
@optional
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;
@end
在后续的学习使用中,你会发现你创建了什么类型的task,那么也只会该类型taskDelegate中定义的代理事件会被触发,犹如你创建了一个NSURLSessionDownloadTask,那么只可能会触发NSURLSessionDownloadDelegate及其父协议中的方法,不可能会触发NSURLSessionDataDelegate中的代理事件。
通常来说,当你创建了一个session,并使用该session创建task并执行(一定要记得执行),那么代理方法将会想继被执行,可以参考下图
NSURLSessionTask
NSURLSessionTask - a cancelable object that refers to the lifetime
of processing a given request。
从描述中可以看出,它为一个处理给定网络请求的可取消对象,换句话说你可以通过NSURLSessionTask对象中声明的方法控制一个请求的开始、暂停、取消。
从图中,可以看出NSURLSessionTask对象及其子类的层级结构。
我们使用NSURLSessionDataTask用于获取或上传表单数据,当你查看AFNetworking封装的GET请求,你会发现其创建了NSURLSessionDataTask对象并发起网络请求任务。
我们使用NSURLSessionUploadTask用于上传二进制数据/文件,如果当前待上传对象是内存对象,则使用传输二进制的方式便捷;如果当前待上传对象是存于沙盒中,则根据其路径的方式便捷。当你查看AFNetworking封装的POST请求,你会发现其使用了NSURLSessionUploadTask对象并发起网络请求任务。
我们使用NSURLSessionDownloadTask用于下载数据。
总结
对于使用NSURLSession发送http网络请求,通常的步骤为
创建task并执行
创建session 的配置项
Create a session configuration. For background sessions, this configuration must contain a unique identifier. Store that identifier, and use it to reassociate with the session if your app crashes or is terminated or suspended.根据指定的configuration object配置项对象,创建session
Create a session, specifying a configuration object and, optionally, a delegate.根据指定的session对象,创建符合需求类型的task并执行-[NSURLSessionTask resume]
Create task objects within a session that each represent a resource request. These task objects are subclasses of NSURLSessionTask—NSURLSessionDataTask, NSURLSessionUploadTask, or NSURLSessionDownloadTask, depending on the behavior you are trying to achieve.
Each task starts out in a suspended state. After your app calls resume on the task, it begins downloading the specified resource.
接受数据并处理
当我们使用NSURLSession相关类发送网络请求时,我们接受响应有两种方式:
- To a completion handler block that is called when a transfer finishes successfully or with an error.
通过异步的completion handler block回调,通过这张方式接受数据的优点是你可以直接得到最终的结果,并不需要你处理上文中提到的代理方法,缺点是你不能细颗粒度的去观察当前请求的响应情况。例如你不能获取当前的上传下载进度,你也不能做一些权限验证等操作
- By calling methods on the session’s delegate as data is received and when the transfer is complete.
通过代理接收并处理数据,优缺点与前一种方式相反。
重要重要
你可以''同时''通过第一种方式获取并处理数据,通过第二种方式设置权限验证、进度处理等业务。