一、 NSURLSession 基础知识
NSURLSession
是协调
一组相关的网络数据传输任务的对象,本身是不会进行请求的,而是通过创建 task (NSURLSessionTask)
的形式进行网络请求(resume()方法的调用)。
1.同一个
NSURLSession
可以创建多个 task,并且这些 task 之间的 cache 和 cookie 是共享的。
2.如果您的应用程序创建一个或多个NSURLSession
实例,每个实例协调一组相关的数据传输任务。
通过NSURLSession 发起一个网络请求 一般有如下几步:
- 1、创建NSURLSession对象
创建 NSURLSession 对象之前 根据需求 可能 会 创建一个NSURLSessionConfiguration配置请求。
- 2、通过session对象发起网络请求,并获取task对象
- 3、 调用[task resume]方法发起网络请求
NSURLSession
的使用相对于之前的NSURLConnection
更简单,而且不用处理Runloop
相关的东西。
NSURLSession相关类为 :
- NSURLSession
- NSURLSessionConfiguration
- NSURLSessionDelegate
- NSURLSessionTask
- NSURLSessionTaskMetrics
- NSURLSessionTaskTransactionMetrics
二、NSURLSession 创建方式
NSURLSession
:请求会话对象,可以用系统提供的单例对象,也可以自己创建。
-
NSURLSession
有三种方式创建:1、
sharedSession
:共享的单例会话对象
(没有配置对象),用于基本请求,可以和其他使用这个session的task共享连接和请求信息,共享会话使用当前设置的全局NSURLCache
,NSHTTPCookieStorage
和NSURLCredentiaStorage
对象
有关更多信息,请参见:shareSession-
2、
sessionWithConfiguration
:使用指定的会话配置创建会话。- 2.1、configuration:一个配置对象,用于指定某些行为,例如缓存策略,超时,代理,管道,支持的TLS版本,cookie策略,凭据存储等。有关更多信息,请参见 NSURLSessionConfiguration
-
3、
sessionWithConfiguration:delegate:delegateQueue
: 使用指定的会话配置、委托和操作队列创建会话。 如果想更好的控制请求过程以及回调线程,需要上面的方法进行初始化操作,并传入delegate
来设置回调对象和回调的线程。- 3.1、 delegate:会话委托对象,用于
处理对身份验证问题、做出缓存决策以及处理其他与会话相关的的事件的请求
。如果为nil,则该类只能与接受完成处理程序的方法一起使用。
从继承关系上,我们就可以理解在初始化的时候,只通过设置NSURLSession对象的delegate就可以了。因为根据不同的task,其实就是设置了不同的delegate。这个设计避免了多次设置delegate的情况,同时也根据不同的task实现不同的delegate方法。
注意:session对象保持对委托的强引用,直到你的应用程序退出或显式地使 会话失效。 如果您没有通过调用invalidateAndCancel 或 finishTasksAndInvalidate 方法来使会话 无效,则您的应用程序会 泄漏内存,直到退出。
- 3.2、 delegateQueue:用于
调度 委托调用
和完成处理程序的操作队列
。 该队列应该是一个串行队列
,以确保回调的正确顺序。 如果为nil,则会话将创建一个串行操作队列,用于执行所有委托方法调用和完成处理程序调用。
- 3.1、 delegate:会话委托对象,用于
@property (class, readonly, strong) NSURLSession *sharedSession;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id )delegate delegateQueue:(nullable NSOperationQueue *)queue;
有关更多信息,请参见 sessionWithConfiguration:delegate:delegateQueue:
三、NSURLSessionConfiguration 会话配置方式
NSURLSessionConfiguration 负责对 NSURLSession 初始化时进行配置。
通过 NSURLSessionConfiguration 可以设置请求的Cookie、密钥、缓存、请求头等参数,将网络请求的一些配置参数从 NSURLSession 中分离出来。
NSURLSessionConfiguration 对象定义了使用 NSURLSession 对象 上传和 下载 数据时使用的行为和策略。当上传或下载数据时,创建配置对象总是您必须采取的第一步。您可以使用此对象来配置超时值、缓存策略、连接要求和其他类型的信息,这些信息您打算与 NSURLSession 对象一起使用。
在使用 NSURLSessionConfiguration 对象初始化一个会话对象之前,适当地配置它是很重要的。会话对象复制您提供的配置设置,并使用这些设置来配置会话。配置完成后,会话对象(session)将忽略您对NSURLSessionConfiguration对象所做的任何更改。如果您需要修改您的传输策略,您必须更新会话配置对象并使用它来创建一个新的 NSURLSession 对象。
请注意:在某些情况下,这个配置中定义的策略可能会被一个为任务提供的 NSURLRequest 对象指定的策略所覆盖。 除非会话的策略更具限制性,否则将遵守在请求对象(NSURLRequest)上指定的任何策略。 例如,如果
会话配置
指定不允许使用蜂窝网络(会话配置限制使用蜂窝网络),则NSURLRequest对象将无法请求蜂窝网络。
3.1、NSURLSessionConfiguration 会话配置的类型
类方法,创建对象
- 3.1.1、defaultSessionConfiguration:
默认会话配置
,使用基于磁盘的持久缓存
(将结果下载到文件时除外),并将账户信息存储在用户的钥匙串中。 它还将cookie(默认情况下)存储在与NSURLSession和NSURLDownload类相同的共享cookie存储中。
注意:修改 返回的会话配置对象
不会影响
以后调用defaultSessionConfiguration方法 返回的任何配置对象 ,并且不会更改现有会话的默认行为。因此,将 返回的 对象用作其他自定义的起点 始终是安全的。
@property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
@property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
有关更多信息,请参见:defaultSessionConfiguration
- 3.1.2、ephemeralSessionConfiguration :
临时会话配置
,跟defaultSessionConfiguration不同的是 会话对象不将缓存、凭据存储或任何与会话相关的数据存储到磁盘。相反,与会话相关的数据存储在RAM中(只会存在内存里),所以当您的应用使会话无效时,将自动清除所有临时会话数据。 此外,在iOS中,暂停应用程序时不会自动清除内存中的缓存,但是如果用户退出并重新启动您的应用,则保证不会存在。
使用临时会话的主要优点是隐私。 通过不将可能敏感的数据写入磁盘,可以减少数据被以后拦截和使用的可能性。 因此,临时会话非常适合Web浏览器和其他类似情况下的私有浏览模式。由于临时会话不会将缓存的数据写入磁盘,因此缓存的大小受可用RAM的限制,可能会降低感知的性能,具体取决于您的应用程序。
有关更多信息,请参见:ephemeralSessionConfiguration
- 3.1.3、 backgroundSessionConfigurationWithIdentifier: :使系统在单独的进程中执行上载和下载任务的一个配置对象(
后台会话配置对象
),该会话配置对象允许程序在后台执行HTTP和HTTPS上传或下载任务。另外,系统会根据设备的负载程度决定分配下载的资源,因此有可能会很慢甚至超时失败- identifier:配置对象的唯一标识符。 此参数不能为nil或为空字符串。
注意:1. 如果iOS应用
被系统终止并重新启动
,则该应用可以使用相同的标识符)来创建新的配置对象和会话,并检索终止时正在进行的传输状态,可以使用同一个 identifier创建configuration和session,并且能恢复终止时的传输状态。此行为仅适用于系统正常终止应用程序的情况 。
2.如果用户从多任务屏幕中终止
该应用程序,则系统会取消所有会话的后台传输。此外,系统不会自动重新启动由用户强制退出的应用程序。用户必须明确重新启动该应用程序,然后才能再次开始传输。
有关更多信息,请参见:backgroundSessionConfigurationWithIdentifier:
有关使用后台配置的示例,请参阅Downloading Files in the Background.
四、NSURLSessionTask 对象的创建
NSURLSessionTask 类是 URL 会话中任务的基类,通过 request 对象或 URL 创建,但一般不会直接是 NSURLSessionTask 类,而是基于不同任务类型,通过调用 NSURLSession 实例中的一个任务创建方法来创建任务。您调用的方法决定了任务的类型。
NSURLSessionTask 类型:
-
1、NSURLSessionDataTask:处理普通的
Get
、Post
数据请求。使用 NSURLSession 的相关方法来创建NSURLSessionDataTask实例。 默认(default
),临时(ephemeral
)和共享(shared
)会话(sessions
)均支持它们,但后台会话(background sessions
)不支持它们。- 1.1、 NSURLSessionUploadTask:
处理上传请求
,通过request创建,在上传时指定文件源或数据源(以数据流的方式进行上传,这种方式好处就是大小不受限制,上传需要服务器端脚本支持)。此外,在后台会话中支持上传任务。是NSURLSessionDataTask的子类。
有关更多信息,请参见:NSURLSessionUploadTask
- 1.1、 NSURLSessionUploadTask:
2、NSURLSessionDownloadTask:
处理下载任务
。下载任务直接将服务器的响应数据写入一个临时文件
,以便在数据从服务器到达时为您的应用提供进度更新
。 当您在后台会话中使用下载任务
时,即使您的应用处于挂起状态
或未运行
,这些下载也会继续。您可以暂停(取消)
下载任务,并在以后恢复它们(如果服务器支持断点下载的话
)。 您也可以恢复由于网络连接问题而失败的下载。
有关更多信息,请参见:NSURLSessionDownloadTask3、NSURLSessionStreamTask:
基于流的URL会话任务
。使用 NSURLSession 的相关方法来创建NSURLSessionStreamTask实例。NSURLSessionStreamTask对象执行同步读取和写入,这些读取和写入将依次排队和执行,并在会话委托队列上完成时调用处理程序。 如果取消该任务,则所有排队的读写操作将以适当的错误调用其完成处理程序。
有关更多信息,请参见:NSURLSessionStreamTask
常见方法:
#pragma mark ------------ 普通数据请求方法 ------------
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
#pragma mark ------------ 上传方法 ------------
//上传NSData类型的数据
//以数据流的方式进行上传,这种方式好处就是大小不受限制
// bodyData 请求的正文数据
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
//创建一个任务,该任务执行HTTP请求以上传指定的文件
//fileURL 要上传本地的文件的URL
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
//提供URL、缓存策略、请求类型等的URL请求对象。
//request 通过流数据初始化的请求对象,上传流数据
//这个请求对象request中的body流和body数据被忽略,而由fromData提供,会话调用其委托的URLSession:task:needNewBodyStream:方法来提供body数据。
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
#pragma mark ------------ 下载方法 ------------
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
//通过之前已经下载的数据来创建下载任务,以恢复先前取消或失败的下载,支持断点下载
//resumeData 之前已经下载的数据。
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
#pragma mark ------------ 基于流的任务方法 ------------
//创建 一个 给定主机 和 端口 的双向流任务
- (NSURLSessionStreamTask *)streamTaskWithHostName:(NSString *)hostname port:(NSInteger)port;
//使用NSNetService创建一个双向流任务来标识端点。NSNetService将在任何IO完成之前被解析。
- (NSURLSessionStreamTask *)streamTaskWithNetService:(NSNetService *)service;
/*
minBytes:读取的最小字节数
maxBytes:读取的最大字节数
timeout:读取字节超时。如果读取未在指定的时间间隔内完成,则读取将被取消,并且会调用errorCompleteHandler。 传递0以防止读取超时。
completionHandler :读取所有字节或发生错误时调用的完成处理程序。 该处理程序在委托队列上执行。
该完成处理程序采用以下参数:
data: 从流中读取的数据
atEOF:流是否到达文件结尾(EOF),从而无法读取更多数据
error:一个错误对象,指示读取失败的原因,如果读取成功,则为nil
*/
- (void)readDataOfMinLength:(NSUInteger)minBytes
maxLength:(NSUInteger)maxBytes
timeout:(NSTimeInterval)timeout
completionHandler:(void (^)(NSData *data, BOOL atEOF, NSError *error))completionHandler;
/*
异步将指定的数据写入流,并在完成时调用处理程序
data:要写入的数据
timeout :写入字节超时。 如果写操作未在指定的间隔内完成,则取消写操作,并且会调用errorCompleteHandler。 传递0以防止写超时。
*/
- (void)writeData:(NSData *)data
timeout:(NSTimeInterval)timeout
completionHandler:(void (^)(NSError *error))completionHandler;
//获取流
//完成所有已排队的读取和写入,然后调用URLSession:streamTask:didBecomeInputStream:outputStream:delegate消息。
// 收到该消息时,任务对象被视为已完成,并且不会再收到任何委托消息。
- (void)captureStreams;
//完成所有排队的读写操作,然后关闭底层套接字的读端。
//调用此方法后,可以继续使用writeData:timeout:completionHandler:方法写入数据。
//调用此方法后,对readDataOfMinLength:maxLength:timeout:completionHandler:的任何调用都会导致错误。
- (void)closeRead;
//在调用closeWrite方法之后,你可以使用readDataOfMinLength:maxLength:timeout:completionHandler:方法继续读取数据。
//在调用这个方法之后,任何对writeData:timeout:completionHandler:的调用都会导致一个错误。
//因为服务器可能会继续向客户端写入字节,所以建议您继续读取,直到流到达文件结束符(EOF)。
//完成所有排队的读写操作,然后关闭底层套接字的写端。
- (void)closeWrite;
//完成所有进入队列的读写操作,并建立安全连接。
//使用URLSession:task:didReceiveChallenge:completionHandler:方法,将身份验证回调发送到会话的委托。
- (void)startSecureConnection;
#pragma mark ------------ 控制任务状态 ------------
//取消当前请求。任务会被标记为取消,并在未来某个时间调用URLSession:task:didCompleteWithError:方法。
-(void)cancel;
//开始或继续请求,创建后的task默认是挂起的,需要手动调用resume才可以开始请求。
//如果任务被挂起,则恢复任务。
-(void)resume;
//挂起当前请求(暂时中止任务)。主要是下载请求用的多一些,
//普通请求挂起后都会重新开始请求。下载请求挂起后,
//只要不超过NSURLRequest设置的timeout时间,调用resume就是继续请求。
-(void) suspend;
常见属性:
#pragma mark ------- 获取任务进展 -------
//进度,整个任务进度的表示
@property (readonly, strong) NSProgress *progress;
//task 期望在请求体中发送的字节数,和 Content-Length of the HTTP request 有关
@property (readonly) int64_t countOfBytesExpectedToSend;
//task 期望在响应体中从服务器接收到的字节数,通常来自HTTP响应的内容长度标题。
@property(readonly) int64_t countOfBytesExpectedToReceive;
// task 在响应体中从服务器接收到的字节数,实际接受的字节数。
@property (readonly) int64_t countOfBytesReceived;
// task 在请求体中发送给服务器的字节数,实际发送的字节数。
@property (readonly) int64_t countOfBytesSent;
#pragma mark ------- 获取常规任务信息 -------
//当前任务的状态 :活动,暂停,正在取消或完成的过程,
//可以通过KVO的方式监听状态的改变。
@property(readonly) NSURLSessionTaskState state;
//此任务的标识符,由所属会话分配且唯一,多个session之间可能存在相同的标识
@property (readonly) NSUInteger taskIdentifier;
//主要用于重定向操作,用来记录重定向前的请求(创建任务时传递的原始请求对象)
//如果这是一个流任务,可能是nil
@property (nullable, readonly, copy) NSURLRequest *originalRequest;
//该任务当前正在处理的URL请求对象。一般和originalRequest是一样的,除非发生重定向才会有所区别
@property (nullable, readonly, copy) NSURLRequest *currentRequest;
//在给定会话中唯一标识任务的标识符
@property(readonly) NSUInteger taskIdentifier;
//服务器对当前活动请求的响应。
@property(nullable, readonly, copy) NSURLResponse *response;
//任务描述
@property(copy) NSString *taskDescription;
//您希望主机处理任务的相对优先级,指定为0.0(最低优先级)到1.0(最高优先级)之间的浮点值。您可以随时指定或更改任务的优先级,但是并非所有网络协议都可以在任务启动后响应更改。 没有API可让您从主机的角度确定任务的有效优先级。
@property float priority;
五、AFNetWorking 的简单使用
AFNetworking4.0 是对NSURLSession的封装,之前版本有NSURLConnection的封装,现在已经被废弃。
简单聊一下,为啥AF要弃用之前的NSURLConnection封装,改成对NSURLSession封装。
首先,NSURLSession是在iOS7.0的时候苹果推出来的。而NSURLSession又能支持Http2.0的。大家都知道Http是基于TCP协议的,早期的Http是短连接的,每次传输数据都需要重新连接,而每次连接的话需要进行三次握手,这就造成了资源以及时间的浪费。然后,在Http2.0的时候更新了Connection:keep-alive选项,这个优化项,使客户端与服务器在相同config的时候复用了同一个TCP连接,减少了每次请求的时间,提升了数据的传输速率。所以,AFNetworking也果断的改变成对NSURLSession的封装。
了解一下AFNetWorking 的 体系结构
NSURLSession
主要对象NSURLSession对象进行了进一步的封装,包含以下核心的类:
AFURLSessionManager
AFHTTPSessionManager
Serialization
提供了与解析数据相关
的操作接口,包含以下核心的类:
-
AFURLRequestSerialization
(请求序列化协议) -
AFHTTPRequestSerializer
(请求序列化父类)AFJSONRequestSerializer
AFPropertyListRequestSerializer
-
AFURLResponseSerialization
(响应序列化协议) -
AFHTTPResponseSerializer
(响应序列化父类)AFJSONResponseSerializer
AFXMLParserResponseSerializer
-
AFXMLDocumentResponseSerializer
(macOS) AFPropertyListResponseSerializer
AFImageResponseSerializer
AFCompoundResponseSerializer
AFHTTPResponseSerializer 和 AFHTTPRequestSerializer 都'符合' AFURLRequestSerialization '和' AFURLResponseSerialization '协议
Additional Functionality
额外功能
-
AFSecurityPolicy
AFSecurityPolicy 提供了与安全性相关
的操作接口 -
AFNetworkReachabilityManager
AFNetworkReachabilityManager 提供了与网络状态相关的操作接口
UIKit,提供了大量网络请求过程中与UI界面显示相关的操作接口,通常用于网络请求过程中提示,使用户交互更加友好,包含以下核心的分类/类:
AFAutoPurgingImageCache
AFImageDownloader
AFNetworkActivityIndicatorManager
UIActivityIndicatorView+AFNetworking
UIButton+AFNetworking
UIImageView+AFNetworking
UIProgressView+AFNetworking
UIRefreshControl+AFNetworking
WKWebView+AFNetworking
初始化
1、AFHttpSessonManager 继承 自AFURLSessonManager。
2、AFHttpSessonManager 内部实现并不是一个单例,而是每次都会创建一个新的HttpSessonManager对象。
3、其中父类(AFURLSessonManager)的初始化方法主要设置了configuration,并且通过设置maxConcurrentOperationCount = 1设置了一个串行队列。以及解析的方式、网络状态的监听并创建了一个lock来保证线程安全等
-
1.AFHTTPSessionManager 类初始化
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// 拼接路径
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
// 设置baseURL
self.baseURL = url;
// 请求序列化类
self.requestSerializer = [AFHTTPRequestSerializer serializer];
// 响应序列化类
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
2.AFURLSessionManager 类初始化
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
// 设置初始化NSURLSessionConfiguration
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
// 设置操作队列及控制最大并发数
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// 设置NSURLSession
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
// 解析方式,设置默认最后解析接口返回数据类(用什么方式去解析)
self.responseSerializer = [AFJSONResponseSerializer serializer];
// 设置一些服务器认证请求
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
// 实时监控当前网络状况
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
// 加锁 来保证线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
// 这是为了防止后台回来,重新初始化这个session,一些之前的后台请求任务,导致程序的crash。
// 将所有回调清nil
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
请求方法
-
1.AFHTTPSessionManager 请求方法
// GET
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
headers:(nullable NSDictionary *)headers
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;
// HEAD
- (NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary *)headers
success:(void (^)(NSURLSessionDataTask * _Nonnull))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;
// POST
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary *)headers
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
// PUT
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
// PATCH
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
// DELETE
- (NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
- GET:用于获取服务器上的数据,可将参数拼接在请求URL后面,上传参数有限制,默认为1024;
- HEAD:用于获取服务器响应首部,不包含实体部分,方便用于根据服务器响应字段,更好地在对请求方式首部进行设置,更精确得去获取到自己所需要的数据;
- POST:用于获取服务器上的数据,可将参数放在请求BODY中,参数个数无限制,POST相对应GET请求安全一点;
- PUT:通常用于向服务器发送请求,如果URL不存在,则要求服务器根据请求创建资源,如果存在,服务器就接受请求内容,并修改URL资源的原始内容;
- PATCH:请求服务器对资源进行局部更新,与PUT方法的区别是,PATCH可用于更新局部资源或字段,而PUT则是更新整个资源文件,要求PUT资源一定是完整的;
- DELETE: 请求服务器删除指定的资源;
-
2. AFURLSessionManager 请求方法
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(nullable NSData *)bodyData
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
请求方法的使用:
1、Creating a Download Task
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];
2、Creating an Upload Task
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Success: %@ %@", response, responseObject);
}
}];
[uploadTask resume];
Creating an Upload Task for a Multi-Part Request, with Progress
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Success: %@ %@", response, responseObject);
}
}];
[uploadTask resume];
Creating an Upload Task for a Multi-Part Request, with Progress
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id formData) {
[formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionUploadTask *uploadTask;
uploadTask = [manager
uploadTaskWithStreamedRequest:request
progress:^(NSProgress * _Nonnull uploadProgress) {
// This is not called back on the main queue.
// You are responsible for dispatching to the main queue for UI updates
dispatch_async(dispatch_get_main_queue(), ^{
//Update the progress view
[progressView setProgress:uploadProgress.fractionCompleted];
});
}
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[uploadTask resume];
3、 Creating a Data Task
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://httpbin.org/get"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
请求 AFURLRequestSerialization
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
// 设置请求方式
mutableRequest.HTTPMethod = method;
// 通过监听用户mutableRequest里的属性值是否改变,
//若有则将该属性及属性值设置给mutableRequest
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 设置请求参数字段
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
//设置请求首部及请求参数字段数据body
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 用户通过当前类添加需要设置的首部属性值,在该类中是将属性值加到self.HTTPRequestHeaders中的
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
//若首部中未包含此属性则添加该属性到该请求的首部HTTPHeader中
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) { // 用户自定义 实现拼接参数方法
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
} else {
switch (self.queryStringSerializationStyle) { // 默认样式
case AFHTTPRequestQueryStringDefaultStyle:
// 默认样式进行将字典转换后的参数拼接字符串
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// self.HTTPMethodsEncodingParametersInURI包含@"GET", @"HEAD", @"DELETE",使用拼接在url后面
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
//URL.query判断是否已经有数据参数,若有则直接用&拼接,否则用?拼接
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
// POST请求设置body的默认编码方式
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
// 用NSUTF8StringEncoding编码格式将query参数字符串转为NSData
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
手动监听mutableRequest中网络请求属性的最新值,若出现了改变,则实时更新并设置到mutableRequest中;
将用户设置的请求首部设置给mutableRequest的HTTPHeaderField;
将请求参数进行拼接设置给mutableRequest的HTTPBody;通过对mutableRequest的相关属性设置参数值后,将返回的mutableRequest生成任务Task并开启任务,此时一个请求任务启动,等待网络请求结果!
请求回调代理
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 将delegate与manage之间进行关联
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// 数据请求完成时回调AFURLSessionManagerTaskDelegate的代理方法
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
// AFURLSessionManagerTaskDelegate的代理方法
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
__strong AFURLSessionManager *manager = self.manager;
// 省略部分代码
if (error) { // 出错时的处理
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
});
} else { // 未出错时的处理
dispatch_async(url_session_manager_processing_queue(), ^{
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
}
}
完成数据请求时,会回调didCompleteWithError对数据进行相关处理,这里若manager设置了跟AFURLSessionManagerTaskDelegate关联起来,则在任务完成回调后,将调用AFURLSessionManagerTaskDelegate类中的方法对数据进行处理,数据处理的方式按照responseSerializer对应设置的类型方式进行!
响应处理AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 省略部分代码
for (id serializer in self.responseSerializers) {
NSError *serializerError = nil;
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
return responseObject;
}
}
return [super responseObjectForResponse:response data:data error:error];
}
AFSecurityPolicy安全认证类
随着互联网的快速发展,导致人们的隐私也变得越来越重要了!苹果爸爸一直都很注重用户隐私问题,所以也提倡应更缜密地传输用户数据信息,防止数据泄露!所以在这里也引申出了HTTPS这个概念,HTTPS具体原理可以参考我写的文章 HTTPS协议:叫个外卖咋这么复杂呢!!
- 苹果认证过程
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
// 自定义处理证书方式
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 判断服务器返回的证书是否是服务器信任的
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { // 通过认证
// 若验证通过,生成用NSURLCredential类生成证书
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
// NSURLSessionAuthChallengeUseCredential为使用正式
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
// 无证书时,忽略证书,这是系统默认的做法
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else { // 证书未通过认证,直接取消认证,忽略证书
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
// 忽略证书,系统默认处理方式
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
// 使用completionHandler回调,让系统去对接完成服务器端的认证过程
completionHandler(disposition, credential);
}
}
实现HTTPS认证过程中,我们可以使用证书或非证书认证的方式,这个具体看公司需求,下面我们来看看这个具体过程!
CA机构认证证书
:这种方式可以让客户端开发感觉很爽,只需要将请求URL中的http换成https后,便可以放心地进行数据传输,因为服务器端只需要将用于认证的加密证书配置为CA机构信任认证的证书即可,这样服务器在第一次加密认证时,用认证证书加密服务器公钥发送给客户端时,当加密数据到达客户端后,客户端自带的CA机构证书会认证服务器加密时使用的加密证书,若验证成功后,客户端便可取出服务器用于客户端传输数据到服务器所使用的公钥!
非CA机构认证证书
:这个过程一般是使用服务器端配置的证书,而这个证书并非CA机构认证的,但我们可以先将证书给客户端,让客户端去认可这个配置的证书,完成后面的HTTPS验证过程,若验证通过则双方可进行交换数据!
-
认证过程
1、
当客户端需要对服务器发送到的证书进行认证时,会调用didReceiveChallenge代理方法
;-
2、
若证书是否是服务器所信任的,则通过三种认证模式其中的一种进行认证
:2.1、AFSSLPinningModeNone:验证是否允许非权威证书(自签名)或是认证机构信任的证书,是的话则验证通过;
2.2、AFSSLPinningModeCertificate:获取客户端证书链中是否有证书包含服务器端证书链中的证书,是的话则验证通过;
2.3、AFSSLPinningModePublicKey:从客户端证书链中将证书转换为公钥key,服务器端证书链也一样转换成公钥key,通过各自的公钥key进行判断,若相等则通过验证;
-
若证书验证通过,则生成NSURLCredential证书,使用completionHandler回调,让系统去对接完成服务器端的认证过程
;
AFNetworkReachabilityManager实时监控
AFNetworkReachabilityManager是AFN提供的可以对网络状态进行检测,并在主线程完成监控状态的回调!苹果对需要联网的应用要求很高,就是必须要进行联网检查。另外,当网络发生异常时能够及时提示用户网络已断开,而不是程序问题造成卡顿;
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
// 一共有四种状态
switch (status) {
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"AFNetworkReachability Not Reachable");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"AFNetworkReachability Reachable is WWAN");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"AFNetworkReachability Reachable is WiFi");
break;
case AFNetworkReachabilityStatusUnknown:
default:
NSLog(@"AFNetworkReachability Unknown");
break;
}
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
当我们使用单例sharedManager简单地使用startMonitoring开启网络检测,可以实时检测到网络状态,但是存在一点不足之处
:
但是这里并不能检测到服务器是否真的可达,只能检测设备是否连接到局域网,以及用的WiFi还是WWAN。即使把设备网络关了,立马检测出NotReachable,连接到路由器立马检测出的还是可以连接到WIFI !有时候虽然联网了,但是网络不一定就能上网是一种可能性,另一种可能性是数据包在传输过程中可以会受影响而被丢弃,毕竟数据包在网际层传输,每经过一个路由器默认经过一跳,但达到255跳时,路由器就会自动丢包!
参考文献:NSURLSession.h
苹果官网 NSURLSession
https://www.jianshu.com/p/ac79db251cbf
https://blog.csdn.net/jbr5740/article/details/89598766