AFNetworking
是iOS开发中最常用的第三方开源库之一,它主要用于进行网络请求。AFNetworking
主要是对HTTP协议和iOS网络相关库的封装,读一读源码可以加深对HTTP协议和iOS网络编程的理解。
AFNetworking的结构
AFNetworking
主要分为四个模块:
- 处理请求和回复的序列化模块:Serialization
- 网络安全模块:Security
- 网络监测模块:Reachability
- 处理通讯的会话模块:NSURLSession
其中NSURLSession是最常使用的模块,也是综合模块,它引用了其他的几个模块,而其他几个模块都是独立的模块,所以对AFNetworking
的学习就先从这些单独的模块开始。
Serialization模块
Serialization模块包括请求序列化AFURLRequestSerialization
和响应序列化AFURLResponseSerialization
,它们主要功能:
AFURLRequestSerialization
用来将字典参数编码成URL传输参数,并提供上传文件的基本功能实现。AFURLResponseSerialization
用来处理服务器返回数据,提供返回码校验和数据校验的功能。
Serialization分为四个文件,分别来看这四个文件的内容。
AFURLRequestSerialization.h
先声明了一个协议AFURLRequestSerialization
继承了NSSecureCoding
和NSCopying
来保证所有实现这个序列化协议的序列化器类都有安全编码和复制的能力。协议也定义了序列化的规范方法。(虽然目前只有一个类实现了协议,感觉没啥必要..但这是一种规范,有利于扩展。)
/**
AFURLRequestSerialization协议可以被一个编码特定http请求的对象实现。
请求序列化器(Request serializer)可以编码查询语句、HTTP请求体,如果必须的话,可以自行设置合适的HTTP请求体内容(如:Agent:iOS)。
例如,一个JSON请求序列化器会把请求体Content-Type设置为application/json。
*/
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
/**
返回一个使用了指定参数编码的请求的拷贝。
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
公开属性
HTTP序列化器类AFHTTPRequestSerializer
,实现了AFURLRequestSerialization
协议,并参考了NSMutableURLRequest
类声明了很多请求设置相关属性。
AFHTTPRequestSerializer和其主要公开属性
/**
AFHTTPRequestSerializer实现了AFURLRequestSerialization协议,为查询语句、URL表单编码参数的序列化提供一个具体的实现和默认的请求头,以及状态码和内容类型的校验。
所有的request和response都被鼓励去继承AFHTTPRequestSerializer类,以确保默认方法和属性的一致性。
*/
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
/**
字符串编码方式,默认为NSUTF8StringEncoding
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
缓存策略。默认为NSURLRequestUseProtocolCachePolicy
参考NSMutableURLRequest -setCachePolicy:
*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
是否用cookie来处理创建的请求。默认为YES
参考NSMutableURLRequest -setHTTPShouldHandleCookies
*/
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
创建的请求在收到上个传输(transmission)响应之前是否继续发送数据。
默认为NO(即等待上次传输完成后再请求)
参考NSMutableURLRequest -setHTTPShouldUsePipelining:
*/
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
/**
请求的网络服务类型。
这个服务类型向整个网络传输层次提供了一个关于该请求目的的提示。
(The service type is used to provide the networking layers a hint of the purpose of the request.)
默认为NSURLNetworkServiceTypeDefault
参考NSMutableURLRequest -setNetworkServiceType:
*/
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
/**
请求的超时间隔,单位秒。默认为60秒
参考NSMutableURLRequest -setTimeoutInterval:
*/
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
/**
序列请求的默认请求头。默认值包括
'Accept-Language’ 内容为 'NSLocale +preferredLanguages’ 方法获取的语音
'User-Agent’ 内容为各种bundle的标志已经系统信息
可以使用'setValue:forHTTPHeaderField:’方法添加或删除请求头
*/
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
/**
哪些HTTP请求方法会将参数编码成查询字符串(如:name=xgb&gender=1)。默认为GET, HEAD和DELETE。
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
/**
需要将参数转换成查询语句的HTTP请求方式。默认包括GET、HEAD和DELETE。
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
@end
属性的理解:stringEncoding
一般服务器都是utf-8格式,没必要用到其他。cachePolicy
缓存策略,苹果主要定义了四种可供选择的缓存策略:
/**
NSURLRequestUseProtocolCachePolicy是默认的缓存策略,它使用当前URL的协议中预置的缓存策略,无论这个协议是http,还是说你自己定义协议。
对于常见的http协议来说,这个策略根据请求的头来执行缓存策略。服务器可以在返回的响应头中加入Expires策略或者Cache-Control策略来告诉客户端应该执行的缓存行为,同时配合Last-Modified等头来控制刷新的时机。
关于详细的web缓存处理https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn
*/
NSURLRequestUseProtocolCachePolicy
/**
不使用缓存,相当于请求头的no-store,在进行包含个人隐私数据或银行业务数据等敏感信息的请求时可以使用。
*/
NSURLRequestReloadIgnoringCacheData
/**
NSURLRequestReturnCacheDataElseLoad 这个策略比较有趣,它会一直偿试读取缓存数据,直到无法没有缓存数据的时候,才会去请求网络。
这个策略有一个重大的缺陷导致它根本无法被使用,即它根本没有对缓存的刷新时机进行控制,如果你要去使用它,那么需要额外的进行对缓存过期进行控制。
*/
NSURLRequestReturnCacheDataElseLoad
/**
只读缓存并且即时缓存不存在都不去请求
*/
NSURLRequestReturnCacheDataDontLoad
HTTPShouldHandleCookies
、HTTPShouldUsePipelining
这两个属性一般不需要修改,使用默认即可。networkServiceType
用于设置这个请求的系统处理优先级,这个属性会影响系统对网络请求的唤醒速度,例如FaceTime使用了VoIP协议就需要设置为NSURLNetworkServiceTypeVoIP来使得在后台接收到数据时也能快速唤醒应用,一般情况下不需要用到。timeoutInterval
请求超时时间。HTTPRequestHeaders
其他请求头设置。
API
AFHTTPRequestSerializer
的API分两种,一种是普通的参数请求,另一种是需要上传文件的请求,如下:
/** 通过URL字符串和字典参数来构建请求 */
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
/** 上传文件的API,需要通过实现了AFMultipartFormData协议的formData对象处理待上传文件 */
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
AFMultipartFormData协议
AFHTTPRequestSerializer
实现了对上传文件的支持,AFMultipartFormData
协议就是定义添加需上传文件的方法,有类似以下的方法:
/** 通过URL定位待上传文件 */
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
/** 通过NSInputStream定义待上传文件 */
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
/** 通过NSData数据上传 */
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
AFURLRequestSerialization.m
工具方法
在了解AFURLRequestSerialization
怎么实现之前,先来看下AFURLRequestSerialization.m
定义的一些工具方法:
AFPercentEscapedStringFromString
方法用于将字符串转化成符合标准的URL编码字符串,代码如下:
NSString * AFPercentEscapedStringFromString(NSString *string) {
// does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@";
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
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