关于AFNetWorking的源码分析已经断更了2年有余,现在来填坑了~
AFURLSessionManager & AFHTTPSessionManager
在我们访问网站时,可以想象做是我们在与远方的服务器进行“会话(Session)”。我们通过规定好的协议(如HTTP),告诉服务器,我们需要查询(GET)什么,我们要提交(POST)什么,我们要修改(PUT)什么,我们要删除(DELETE)什么。
我们在这场会话(Session)中,和服务的每一次通话,我们称之为请求(request),而服务器根据我们的请求,返回给我们的结果,被称作响应(response)。
这就是我们平时所进行的网络活动的一个简略的概括。而将这个过程对应到AF及iOS中,则是:
与服务器进行的会话,在AF中通过AFURLSessionManager
来进行管理。而对我们的每一次的请求(request),AFURLSessionManager
都会返回一个NSURLSessionDataTask
对象,来表示这一次的通信过程,并在相应的回调中,返回通信的response。
关于AFURLSessionManager
的实现细节,我们在上一篇中已经描述。而针对到更具体的符合HTTP协议
的会话(Session),在AF中则是由继承自AFURLSessionManager
的AFHTTPSessionManager
来进行管理的。
AFHTTPSessionManager
继承了AFURLSessionManager
返回NSURLSessionDataTask
对象的能力。但是有针对HTTP协议中的GET/HEAD/POST/PUT/PATCH/DELETE
,HTTP manager会返回更加具体的NSURLSessionDataTask
对象。
AFHTTPSessionManager
在了解AFHTTPSessionManager
之前,我们必须对HTTP
协议以及基本的GET/POST/DELETE/PUT
方法有所了解,这里就不再冗诉。
header
我们先来看一下AFHTTPSessionManager
的头文件,看看它都提供了那些接口。这也是我们阅读源码时常用的方法,通过阅读头文件,先了解类的作用及其对外的接口,然后再看类实现文件,这样可以做到有的放矢的了解别人所实现的类。
AFHTTPSessionManager
的头文件很简单,首先,它是继承自AFURLSessionManager
:
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
接下来是用于构建HTTP request以及解析 response的序列化类,用户可以自定义这些类,只要符合AFURLRequestSerialization 和 AFURLResponseSerialization协议即可。,如果没有指定,则会使用默认的AFHTTPRequestSerializer
和 AFJSONResponseSerializer
:
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
然后是管理会话Session的安全策略类:
///-------------------------------
/// @name Managing Security Policy
///-------------------------------
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
接下来是一系列的init
方法,主要作用是指定一个base URL
,这样在后续的操作时,直接填入对应的查询参数或跳转URL即可,AF会在对应的request URL中自动补上base URL
:
///---------------------
/// @name Initialization
///---------------------
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
+ (instancetype)manager;
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration;
最后是针对HTTP协议,用来生成GET/HEAD/POST/PUT/PATCH/DELETE
请求task的工具方法,这一部分提供的接口很多,先大致看一下就好,其实可以分为6大类,分别对应HTTP的方法GET/HEAD/POST/PUT/PATCH/DELETE
。在每一个大类下,有会根据不同的参数,提供了有细微差别的不同接口。
///---------------------------
/// @name Making HTTP Requests
///---------------------------
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)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;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
OK,上面就是AFHTTPSessionManager
的头文件的全部。可以分为:
初始化init方法
,HTTP序列化
,安全策略
,HTTP请求接口
四大部分。对于安全策略
,我们先不去关心,在本章中,我们会去分析初始化init方法
,以及HTTP请求接口
。在HTTP请求接口
中,又会调用到HTTP序列化
的相关方法。
初始化init方法
AFHTTPSessionManager
的初始化方法有三个:
+ (instancetype)manager;
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER; // NS_DESIGNATED_INITIALIZER 宏用来指定这是AF推荐的初始化方法
实现如下:
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
可见这些初始化方法,最终都会调用到AF推荐的初始化方法,来真正的初始化:
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
// 调用super 来初始化
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer]; // 默认使用 AFHTTPRequestSerializer
self.responseSerializer = [AFJSONResponseSerializer serializer]; // 默认使用 AFJSONResponseSerializer. 所以,要解析其他格式的response,如XML,则需要自己写符合协议AFURLResponseSerialization
return self;
}
在init方法里面,先调用了父类的initWithSessionConfiguration
来初始化自己,然后是记录baseURL
(确保baseURL以’/'结尾),然后设置默认的request/response序列化对象为AFHTTPRequestSerializer 对象
和AFJSONResponseSerializer 对象
。
HTTP请求接口
AFHTTPSessionManager
针对不同的HTTP request method,设计了若干对应method的生成NSURLSessionDataTask对象
的请求接口。其全部可以按照HTTP的请求类型,分为6大类:
GET
、HEAD
、POST
、PUT
、PATCH
、DELETE
6大类。我们将最复杂的POST
留在最后分析,首先从GET方法看起。
GET
GET请求
用来从服务器查询获取信息。GET请求
是一个幂等操作
,所谓幂等操作,是指在GET请求
中,多次发送GET请求
,其返回的结果是一致的,而且不会对服务器内容造成任何影响。
因此幂等操作对服务器来说,是安全的,因为它不会改变任何服务器的状态。
AFHTTPSessionManager
为GET请求
设计的接口有三个:
-
(nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; -
(nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; -
(nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
实质上只有一个,因为前两个接口实际上只会调用最后一个接口来实现具体的逻辑:
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
那我们就来看一下,最后一个GET
接口是如何实现的:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
headers:headers
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
实现逻辑很简单,核心是调用了AFHTTPSessionManager
的内部方法:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
关于dataTaskWithHTTPMethod
方法是如何实现的,我们暂且不表。这里仅需要注意到其第一个参数HTTPMethod
,这意味着除了GET方法,它也可以接受其他其他HTTP method类型。实际上,对于全部6个HTTP method
, 最后都会调用到dataTaskWithHTTPMethod
方法来返回NSURLSessionDataTask对象
。
不过,这里也有例外,就是对POST
方法,它不一定是最终调用的dataTaskWithHTTPMethod
方法。关于这一点,我们还是稍后再说。
以上,就是GET
的实现。很简单。这里可以学习到一个技巧,就是,我们可能会向外部提供接收各种类型参数的接口,但是在内部实现,我们可以用一个统一的接口来写具体的逻辑。
HEAD
GET
请求之后,是HEAD
请求。
HEAD
请求的定义在w3.org中是这么说的:
The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.
The response to a HEAD request MAY be cacheable in the sense that the information contained in the response MAY be used to update a previously cached entity from that resource. If the new field values indicate that the cached entity differs from the current entity (as would be indicated by a change in Content-Length, Content-MD5, ETag or Last-Modified), then the cache MUST treat the cache entry as stale.
简单来说就是:
HEAD方法与GET类似,但是HEAD并不返回消息体。在一个HEAD请求的消息响应中,HTTP投中包含的元信息应该和一个GET请求的响应消息相同。这种方法可以用来获取请求中隐含的元信息,而无需传输实体本身。这个方法经常用来测试超链接的有效性,可用性和最近修改。
一个HEAD请求响应可以被缓存,也就是说,响应中的信息可能用来更新之前缓存的实体。如果当前实体缓存实体阈值不同(可通过Content_Length、Content-MD5、ETag或Last-Modified的变化来表明),那么这个缓存被视为过期了。
其实HEAD请求
可以看做是简化版的GET
,HEAD请求
表示了在response 中,仅需要reponse header,而不需要response body。这样做可以减少网络传输的负担。
HEAD请求
特别适用在优先的速度和带宽下
- 检查资源的有效性。
- 检查超链接的有效性。
- 检查网页是否被串改。
- 多用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等
AFHTTPSessionManager
为HEAD请求
提供的接口有2个,实质上也仅有一个实现,第一个也已经被AF标志位废弃:
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
实现也很简单,也是调用了dataTaskWithHTTPMethod
:
- (NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask * _Nonnull))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters headers:headers uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, __unused id responseObject) {
if (success) {
success(task);
}
} failure:failure];
[dataTask resume];
return dataTask;
}
PUT
接下来是PUT请求
。
PUT请求
和 POST请求
的概念总会让人产生混淆。这两个请求都可以做资源的插入与更新。
PUT请求
和 POST请求
最大的区别在于,PUT请求
是幂等的,即多次请求相同的PUT
,对于服务器端不会造成不同的结果。举个例子来说,我们要PUT
一篇博客,第一次PUT
,会产生一篇博客,之后再次发送同样的PUT
请求,服务端并不会产生新的博客。
而POST
则不同,POST请求
不是幂等的,即使相同的POST
,多次发送,会产生多次的结果。
另一个区别是,PUT请求
需要指定资源的标识符,而POST请求
则不需要指定标识符。
考虑到我们创建一个资源时(如发布一篇博客),其资源标识符都是在服务器端确定的(例如使用数据库自增长ID),PUT请求无法提前获取到该标识符。
因此,PUT请求
多被用于修改已有的资源,而POST请求
,则用来提交或创建资源。
下面就让我们一起看下,AFHTTPSessionManager
为PUT请求
提供的接口:
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
观察其实现,发现也是最终调用的dataTaskWithHTTPMethod
方法:
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
return [self PUT:URLString parameters:parameters headers:nil success:success failure:failure];
}
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters headers:headers uploadProgress:nil downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
PATCH
PATCH
翻译为中文意为“打补丁”。PATCH
请求的功能和PUT请求类似,都是用于更新资源。而PATCH
和PUT
不同的在于,PUT必须提交资源所有的属性,而PATCH
可以仅提交需要修改的属性。
AFHTTPSessionManager
提供的PATCH请求
接口有两个:
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
其实说到这里,已经很套路了,PATCH的实现同样是调用了dataTaskWithHTTPMethod
方法:
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
return [self PATCH:URLString parameters:parameters headers:nil success:success failure:failure];
}
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters headers:headers uploadProgress:nil downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
DELETE
DELETE方法
就是通过http请求删除指定的URL上的资源啦,DELETE请求
一般会返回3种状态码:
- 200 (OK) - 删除成功,同时返回已经删除的资源
- 202 (Accepted) - 删除请求已经接受,但没有被立即执行(资源也许已经被转移到了待删除区域)
- 204 (No Content) - 删除请求已经被执行,但是没有返回资源(也许是请求删除不存在的资源造成的)
AFHTTPSessionManager为DELETE请求提供的接口有两个:
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
实现如下:
- (NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
return [self DELETE:URLString parameters:parameters headers:nil success:success failure:failure];
}
- (NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters headers:headers uploadProgress:nil downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
dataTaskWithHTTPMethod
前面说了这么多HTTP 请求的接口,最终,实质上都会调用到同一个接口:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
dataTaskWithHTTPMethod
这个接口会返回一个NSURLSessionDataTask
,并支持GET/HEAD/PUT/PATCH/DELETE方法
。
现在,我们就来看一下dataTaskWithHTTPMethod
这个神奇的方法是怎么实现的。
在看具体代码之前,我们先回过头来了解一下HTTP协议
的格式是什么样的。关于HTTP的内容,多来自于这里。
HTTP协议
HTTP使用统一资源标识符来(URI
)传输数据和建立连接。统一资源定位符(URL
)是一种特殊的URI
,包含了查找资源的足够信息,如用户姓名参数等。我们平常使用的就是URL
。下面是一个标准的URL
例子:
http://www.fishbay.cn:80/mix/76.html?name=kelvin&password=123456#first
至于每一个部分代表了什么意思,可以参看本文所引用的文章。
当我们的请求在网络上传输时,包括URL及参数,都会被打包成序列化的文本信息包,HTTP的请求格式如下:
我们在程序中设置NSURLRequest/NSMutableURLRequest
时候,iOS会自动将我们设置的内容转换为上面格式的文本内容并发送到网络上。
对于最下面的请求数据
,是通过request
的setHTTPBody
方法设置的。对于这一块,我们会在POST方法
中作出讲解。
而开始的地方来看,我们需要填写HTTP 请求的 :
请求行(请求方法,URL),请求头部
请求方法
对应 request
的HTTPMethod 属性
。
URL
对应request
的initWithURL
初始化方法。
请求头部
对应 request
的 setValue:forHTTPHeaderField: 方法
。
OK,有了这些基础知识后,我们在回过头来看dataTaskWithHTTPMethod
方法的源码。
源码
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
// step1. 利用 requestSerializer(AFHTTPRequestSerializer)先生成request
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
// step2. 再填入header fields
for (NSString *headerField in headers.keyEnumerator) {
[request addValue:headers[headerField] forHTTPHeaderField:headerField];
}
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
// step3. 调用父类方法,根据NSURLRequest *request, 返回对应的dataTask
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
dataTaskWithHTTPMethod
方法的层次还是比较清晰的。
- 调用
requestSerializer
的requestWithMethod
方法,返回一个NSMutableURLRequest
。requestWithMethod
方法接受Method,URL,Parameters
三个参数,并返回一个serializer error
。 - 得到
request
后,在根据用户设置的headers
信息,填写头部信息。 - 最终,调用
super class
的dataTaskWithRequest
方法,将组装好的request
传入,并返回对应的dataTask
。
可以看到,对于AFHTTPSessionManager来说,最重要的还是self.requestSerializer requestWithMethod
,对比上面HTTP协议的格式,在requestWithMethod
方法中,完成了除了请求头部外,所有的HTTP 请求的封装。
我们就来详细看一下requestWithMethod
方法的实现。
requestWithMethod:URLString:parameters:error:
requestWithMethod
方法的实现如下:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
// 1. 通过init 方法设置 HTTP请求 的URL
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
// 2. 通过 HTTPMethod 属性设置 HTTP请求 的请求方法
mutableRequest.HTTPMethod = method;
// 3.设置mutableRequest 对应的属性值。这是通过KVO用户设置相关的属性操作
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 4. 将URL参数附加到 request 中,并返回request的copy值
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
逻辑比较清晰,难点在于AF是如何将各个参数附加到request中的:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 根据request headers, 设置request header
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) { // 设置request的头
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 格式化querystring, 将传入的parameters(AF默认是字典格式),转换为一个NSString
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) { // 如果用户提供了queryString的Serialization block,则让用户自定义query string的格式
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) { // 如果用户没有自定义参数格式,则会走这里.AF默认仅提供了一种query stirng 格式:AFHTTPRequestQueryStringDefaultStyle
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters); // query string是这种样式的 @"user=Tim&age=12&sex=man"
break;
}
}
}
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { // 默认 GET, HEAD, DELETE会走这里(参数会放置到http URL中)
if (query && query.length > 0) { // 将query string附加到request URL中(如果有的话)
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; // 当前URL里面是否已经含有query string? 有,则用&连接 query string. 没有,则用 ? 开头来连接query string
}
} else { // 如果不是GET, HEAD, DELETE方法,会走这里。(参数将使用form格式,放置到http body中)
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { // 默认Content-Type 是form类型
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; // 将query string当做data,放到request 的body中
}
return mutableRequest;
}
代码比较长,我们慢慢分析。
首先,AFHTTPRequestSerializer
会先copy一份request:
// 返回copy内容
NSMutableURLRequest *mutableRequest = [request mutableCopy];
然后,设置request header信息:
// 根据request headers, 设置request header
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) { // 设置request的头
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
接下来重点来了,AFHTTPRequestSerializer
会将我们传入的参数,这里默认是NSDictionary字典,转换为一个字符串NSString,并附加到request中。这里如何将参数转换为字符串,AF给了我们两个选择,一个是通过传入AFQueryStringSerializationBlock
的方式,来自定义参数如何转换。另一个是AF给我们的默认实现,将字典类型转换为NSString。
// 格式化querystring, 将传入的parameters(AF默认是字典格式),转换为一个NSString
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) { // 如果用户提供了queryString的Serialization block,则让用户自定义query string的格式
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else { // 由用户自定义的转换参数方式我们不去关心,我们只看这里由AFHTTPRequestSerializer提供的转换方式:
switch (self.queryStringSerializationStyle) { // 如果用户没有自定义参数格式,则会走这里.AF默认仅提供了一种query stirng 格式:AFHTTPRequestQueryStringDefaultStyle
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters); // query string是这种样式的 @"user=Tim&age=12&sex=man"
break;
}
}
}
对于由用户自定义的转换参数方式我们不去关心,我们只看这里由AFHTTPRequestSerializer
提供的转换方式,也就是else
中的内容。
首先,会判断AFHTTPRequestSerializer
当前queryString的序列化方式,AF只有一种,即AFHTTPRequestQueryStringDefaultStyle
。在这种方式下,会调用AFQueryStringFromParameters
,来返回@"user=Tim&age=12&sex=man"
格式的查询字符串。
我们来看一下AFQueryStringFromParameters
函数是如何实现的。它是一个C类型的函数:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
// 将parameters中的每个元素转换为一系列AFQueryStringPair类型,并遍历之。
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
// 将每个AFQueryStringPair 转换为NSString,并存储到mutablePairs 数组中
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
// 将 mutablePairs 数组中的每个元素,通过@"&"连接成整体,并返回该字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
AFQueryStringFromParameters
函数会将传入的NSDictionary
参数转换为AFQueryStringPair
数组。然后,会将数组元素在以字符串数组形式转存,最终,返回以@"&"
连接的字符串。
这里的难点在于,NSDictionary
字典是如何转换为AFQueryStringPair
数组的。它是由AFQueryStringPairsFromDictionary
函数实现的:
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
该函数实质上是调用了NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
方法,其参数本应是一个key
和vaule
类型,然后返回AFQueryStringPair
数组。
因为我们的参数是一个NSDictionary
,所以这里的key是nil
,vaule就是字典本身。
我们来看AFQueryStringPairsFromKeyAndValue
方法是如何实现的:
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey]; // 取出key对应的value
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; // 继续递归, 将value中的key作为key,valuez的key对应的nestedVaule作为vaule
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents; // 最终返回的是AFQueryStringPair的数组 (包含了非集合态的Key 和 value)
}
很明显,AFQueryStringPairsFromKeyAndValue
是一个递归函数。该函数会将Key/Vaule
一层层的分解为AFQueryStringPair
。之所以会是递归的形式,因为value的取值也可能是Key/Vaule
,因此需要进一步的递归分解。
我们这里仅考虑AF的默认情况,即初始值key == nil , value == NSDictionary *
:
当第一次递归时,因为value isKindOfClass:[NSDictionary class]
为TRUE
,因此会进入第一个if分支:
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey]; // 取出key对应的value
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; // 继续递归, 将value中的key作为key,valuez的key对应的nestedVaule作为vaule
}
}
}
上面的代码中,会用for循环遍历NSDictionary的所有key: nestedKey
,并取出nestedKey
所对应的值nestedValue
:
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey]; // 取出key对应的value
。。。
}
如果nestedValue 存在的话,则会进入下一层递归AFQueryStringPairsFromKeyAndValue
。我们这里传入的是URL参数,因此Vaule一定会存在:
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; // 继续递归, 将value中的key作为key,valuez的key对应的nestedVaule作为vaule
}
在下一层递归的时候,我们同样要传入key和value两个参数:
AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; // 继续递归, 将value中的key作为key,valuez的key对应的nestedVaule作为vaule
在这里会根据key是否存在,决定传入下层递归的key值。在第一次调用AFQueryStringPairsFromKeyAndValue
方法时,key==nil,因此,下一层递归的参数为:
AFQueryStringPairsFromKeyAndValue(nestedKey, nestedValue)]; // 继续递归, 将value中的key作为key,valuez的key对应的nestedVaule作为vaule
到这里,nestedKey
和nestedValue
的值类型都是NSString
。
然后,我们进入下一层递归调用AFQueryStringPairsFromKeyAndValue(nestedKey, nestedValue)
。
因为这一层的递归中,value的类型是NSString,因此会进入AFQueryStringPairsFromKeyAndValue
函数的最后一个分支判断:
else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
也就是直接用key
和 vaule
创建一个AFQueryStringPair
对象,然后将这个AFQueryStringPair
对象添加到mutableQueryStringComponents
数组中。
然后,返回这个mutableQueryStringComponents
数组到上一层递归中:
return mutableQueryStringComponents; // 最终返回的是AFQueryStringPair的数组
当返回到上一层递归后,会进入遍历NSDictionary
下一个元素,在重复一遍上述的过程。最终,会将NSDictionary
中所有的key/vaule对
都转换为AFQueryStringPair
对象,并存储到mutableQueryStringComponents
数组中返回。
上面就是NSDictionary
是如何转换为AFQueryStringPair数组
的过程。让我们再回过头来再看AFQueryStringPair数组
是如何被处理的:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
// 将parameters中的每个元素转换为一系列AFQueryStringPair类型,并遍历之。
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
// 将每个AFQueryStringPair 转换为NSString,并存储到mutablePairs 数组中
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
// 将 mutablePairs 数组中的每个元素,通过@"&"连接成整体,并返回该字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
在AFQueryStringFromParameters函数
中,会调用AFQueryStringPairs
的URLEncodedStringValue方法
来返回NSString值。
我们来看一下AFQueryStringPairs
类的定义,就会知道URLEncodedStringValue方法
是先把key/vaule中URL所用到的关键字做替换后,再以@"%@=%@"
的形式来连接。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
@implementation AFQueryStringPair
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.field = field;
self.value = value;
return self;
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
// 通过description方法,可以将NSNumber,NSString, 统一转换为NSString。 BOOL类型的NSNumber将转换为字符0 or 1。 这里是不是太取巧了?
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
@end
这里我们来看一下,AF是如何替换URL关键字的:
/**
Returns a percent-escaped string following RFC 3986 for a query string key or value.
RFC 3986 states that the following characters are "reserved" characters.
- General Delimiters: ":", "#", "[", "]", "@", "?", "/"
- Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
should be percent-escaped in the query string.
- parameter string: The string to be percent-escaped.
- returns: The percent-escaped string.
*/
NSString * AFPercentEscapedStringFromString(NSString *string) {
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
NSUInteger length = MIN(string.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as ????
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
OK,上面说了很多,均是在分析作为NSDictionary
形式的URL参数
,是如何转换为NSString
类型的query string
的。
让我们回到AFHTTPRequestSerializer
的requestBySerializingRequest
方法中:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
// 返回copy内容
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 根据request headers, 设置request header
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) { // 设置request的头
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 格式化querystring, 将传入的parameters(AF默认是字典格式),转换为一个NSString
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) { // 如果用户提供了queryString的Serialization block,则让用户自定义query string的格式
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) { // 如果用户没有自定义参数格式,则会走这里.AF默认仅提供了一种query stirng 格式:AFHTTPRequestQueryStringDefaultStyle
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters); // query string是这种样式的 @"user=Tim&age=12&sex=man"
break;
}
}
}
// 获取到query string 后,将query string放入到request中
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { // 默认 GET, HEAD, DELETE会走这里(参数会放置到http URL中)
if (query && query.length > 0) { // 将query string附加到request URL中(如果有的话)
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; // 当前URL里面是否已经含有query string? 有,则用&连接 query string. 没有,则用 ? 开头来连接query string
}
} else { // 如果不是GET, HEAD, DELETE方法,会走这里。(参数将使用form格式,放置到http body中)
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { // 默认Content-Type 是form类型
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; // 将query string当做data,放到request 的body中
}
return mutableRequest;
}
在上面的步骤中,我们已经获取到了query string
,接下来就剩下最后的工作,将query string
放入到request
中并返回:
// 获取到query string 后,将query string放入到request中
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { // 默认 GET, HEAD, DELETE会走这里(参数会放置到http URL中)
if (query && query.length > 0) { // 将query string附加到request URL中(如果有的话)
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; // 当前URL里面是否已经含有query string? 有,则用&连接 query string. 没有,则用 ? 开头来连接query string
}
} else { // 如果不是GET, HEAD, DELETE方法,会走这里。(参数将使用form格式,放置到http body中)
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { // 默认Content-Type 是form类型
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; // 将query string当做data,放到request 的body中
}
return mutableRequest;
将query string
放入到request
中有两种情况,第一种是对于GET, HEAD, DELETE
方法,参数会放入到request
的URL
中,并确保通过?
来和URL
相连接:
if (query && query.length > 0) { // 将query string附加到request URL中(如果有的话)
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; // 当前URL里面是否已经含有query string? 有,则用&连接 query string. 没有,则用 ? 开头来连接query string
}
第二种情况则是除去GET, HEAD, DELETE
方法后,这里应该是指POST和PATCH方法
,参数不会直接放到request的URL中,而是会放到request的body
中。而在body中的格式,则是遵循form格式
的,其Content-Type
为application/x-www-form-urlencoded
:
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { // 默认Content-Type 是form类型
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; // 将query string当做data,放到request 的body中
至于什么是form格式
,以及POST方法
的相关代码,我们将会在下一篇博客中进行讲述。
总结
本篇文章主要分析了AF针对HTTP协议所设计的AFHTTPSessionManger
的实现。并主要针对HTTP中GET, HEAD, DELETE,PATCH
方法相关的接口进行了深入的分析。
其实对于上面那些接口,简单的思路就是:
AFHTTPSessionManger
生成相应方法格式的request,并将URL参数附加到request中。这种格式化的工作,是交给AFHTTPRequestSerializer
来做的。
最后,通过向父类AFURLSessionManager
传递格式化好的request
,调用父类接口dataTaskWithRequest
,来最终返回dataTask
的。
在这里我们可以看到,AFHTTPSessionManger
主要负责了接口调用的功能,以及从父类继承过来的dataTask的管理功能。而具体的格式化request工作,则是交给了另一个类AFHTTPRequestSerializer
来专门负责。
各个类之间各司其职,也符合设计模式中 职责单一
的原则。