AFNetWorking(3.0)源码分析(三)——AFHTTPSessionManager(1)

关于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中则是由继承自AFURLSessionManagerAFHTTPSessionManager来进行管理的。

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协议即可。,如果没有指定,则会使用默认的AFHTTPRequestSerializerAFJSONResponseSerializer

@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大类:

GETHEADPOSTPUTPATCHDELETE 6大类。我们将最复杂的POST留在最后分析,首先从GET方法看起。

GET

GET请求用来从服务器查询获取信息。GET请求是一个幂等操作,所谓幂等操作,是指在GET请求中,多次发送GET请求,其返回的结果是一致的,而且不会对服务器内容造成任何影响。

因此幂等操作对服务器来说,是安全的,因为它不会改变任何服务器的状态。

AFHTTPSessionManagerGET请求设计的接口有三个:

  • (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请求可以看做是简化版的GETHEAD请求表示了在response 中,仅需要reponse header,而不需要response body。这样做可以减少网络传输的负担。

HEAD请求特别适用在优先的速度和带宽下

  1. 检查资源的有效性。
  2. 检查超链接的有效性。
  3. 检查网页是否被串改。
  4. 多用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等

AFHTTPSessionManagerHEAD请求提供的接口有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请求,则用来提交或创建资源。

下面就让我们一起看下,AFHTTPSessionManagerPUT请求提供的接口:

- (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请求类似,都是用于更新资源。而PATCHPUT不同的在于,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的请求格式如下:

Http请求消息结构

我们在程序中设置NSURLRequest/NSMutableURLRequest时候,iOS会自动将我们设置的内容转换为上面格式的文本内容并发送到网络上。

对于最下面的请求数据,是通过requestsetHTTPBody方法设置的。对于这一块,我们会在POST方法中作出讲解。

而开始的地方来看,我们需要填写HTTP 请求的 :

请求行(请求方法,URL),请求头部

请求方法对应 requestHTTPMethod 属性

URL对应requestinitWithURL初始化方法。

请求头部 对应 requestsetValue: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方法的层次还是比较清晰的。

  1. 调用requestSerializerrequestWithMethod方法,返回一个NSMutableURLRequestrequestWithMethod方法接受Method,URL,Parameters 三个参数,并返回一个serializer error
  2. 得到request后,在根据用户设置的headers信息,填写头部信息。
  3. 最终,调用super classdataTaskWithRequest方法,将组装好的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)方法,其参数本应是一个keyvaule类型,然后返回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

到这里,nestedKeynestedValue的值类型都是NSString

然后,我们进入下一层递归调用AFQueryStringPairsFromKeyAndValue(nestedKey, nestedValue)

因为这一层的递归中,value的类型是NSString,因此会进入AFQueryStringPairsFromKeyAndValue函数的最后一个分支判断:

else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

也就是直接用keyvaule创建一个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函数中,会调用AFQueryStringPairsURLEncodedStringValue方法来返回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 的。

让我们回到AFHTTPRequestSerializerrequestBySerializingRequest 方法中:

- (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方法,参数会放入到requestURL中,并确保通过?来和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-Typeapplication/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 来专门负责。

各个类之间各司其职,也符合设计模式中 职责单一的原则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值