AFNetworking3详解
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 👴🏻👮🏽
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
上面代码的实现主要是通过一个字符集NSMutableCharacterSet
来定义需要进行转码的字符,再通过-[NSString stringByAddingPercentEncodingWithAllowedCharacters]方法来进行转码。
-
AFQueryStringFromParameters
方法用于将字典转换成URL查询字符串。转换格式如下:
字典 {name:xgb, from:gd, machine:[iphone, mac], pet:{a:cat, b:dog}}
转换为 name=xgb&from=gd&machine[]=iphone&machine[]=mac&pet[a]=cat&pet[b]=dog
AFNetworking定义了一个Model类AFQueryStringPair
来存取每一个查询属性,代码如下:
@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 {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
@end
将查询的键值保持起来,然后通过URLEncodedStringValue
方法在需要时进行拼接,并且使用了上面所述的AFPercentEscapedStringFromString
方法进行了URLEncode。接下来再看看AFQueryStringFromParameters
方法的主要实现:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
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];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} 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;
}
其中最主要的方法是AFQueryStringPairsFromKeyAndValue
,它将字典的每一个键值对生成的对应的AFQueryStringPair
对象,例如将machine:[iphone, mac]转换为URLEncodedStringValue
值是machine[]=iphone和machine[]=mac的两个AFQueryStringPair
对象。之后AFQueryStringFromParameters
方法再以'&'符号对它们进行拼接。
-
AFHTTPRequestSerializerObservedKeyPaths
方法定义可以需要被观察的属性(这些属性为公开的属性,可能被用户修改),包括cachePolicy
、HTTPShouldHandleCookies
、HTTPShouldUsePipelining
、networkServiceType
和timeoutInterval
。代码如下:
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
AFHTTPRequestSerializer序列化流程
接下来看下AFHTTPRequestSerializer
的实现。AFHTTPRequestSerializer
在内部扩展定义了一些私有属性,如下:
主要的私有属性
@interface AFHTTPRequestSerializer ()
/**
保存用户修改过的属性,包括AFHTTPRequestSerializerObservedKeyPaths包含的属性。
当用户修改这些属性值时记录起来,创建Request时使用,没修改的使用默认值。
*/
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
/** 真正存储Header的属性。 */
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
/**
用一个串行线程来统一处理Header的修改,避免多线程造成的线程安全问题。
*/
@property (readwrite, nonatomic, strong) dispatch_queue_t requestHeaderModificationQueue;
/** 目前还没啥用的属性。 */
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
/**
用于自定义查询字符串的拼接。
因为AFURLRequestSerialization协议定义的方法-requestBySerializingRequest:withParameters:error:传入的parameters是以字典的形式传入,所以需要将字典拼接成查询字符串,默认是使用AFQueryStringFromParameters方法拼接。
*/
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
@end
通过对AFHTTPRequestSerializer
属性了解,我们就能初步理解AFHTTPRequestSerializer
的流程。先看下初始化方法:
初始化方法
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f);
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
NSString *userAgent = nil;
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
初始化方法主要是对一些属性初始化,以及将HTTP头部按照w3c标准进行了封装,根据AFHTTPRequestSerializerObservedKeyPaths
方法对一些必要的属性使用KVO进行了监听。在这些被监听的属性的setter里面手动地发送通知,避免出现奇怪的异常,例如:
- (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
[self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
_cachePolicy = cachePolicy;
[self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
}
AFHTTPRequestSerializer
构建一般的请求的流程:
- (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);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
主要操作是将修改过的属性对照着设置到NSMutableURLRequest
。继续
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
这一段的主要功能是将参数转换成URL查询语句,并把其设置到HTTP Body里。
上传文件
在了解这部分代码之前,需要先了解一下HTTP协议关于传输multipart数据的相关约定。根据rfc1867规定,传输multipart可以使用以下例子的格式:
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="userName"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics[]"; filename="pic1.png"
Content-Type: image/png
... contents of pic1.png ...
--AaB03x
Content-disposition: form-data; name="pics[]"; filename="pic2.png"
Content-type: image/png
... contents of pic2.png ...
--AaB03x--
定义一个边界boundary,每个上传的各个部分(Part)用--boundary
来分割,再以--boundary--
为结尾。name为服务器端获取文件时使用的参数,filename为本地的文件名。
AFNetworking主要是通过流(Stream)的方式来上传文件,因此AFHTTPRequestSerializer
提供了最基本的流处理方法。
AFHTTPRequestSerializer
使用AFMultipartFormData
协议来定义用户传入需要上传的文件或者数据的方法,然后通过一个实现了该协议的类来管理文件的上传。通过以下方法来生成一个包含文件流的Request:
/**
根据指定请求方法、URL和参数以及Block构造的multipart/form-data类型的HTTP body,返回一个NSMutableURLRequest对象。
请求的多部分(Multipart)自动以流(stream)的形式从硬盘或者内存中读取数据到HTTP body。
这导致生成的NSMutableURLRequest对象有一个HTTPBodyStream属性,所以不要再设置HTTPBodyStream或者HTTPBody给这个对象,以避免覆盖掉流数据。
*/
- (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;
这个过程主要涉及到了以下三个类:
-
AFStreamingMultipartFormData
就是实现了AFMultipartFormData
协议定义的添加上传文件方法的类。 -
AFMultipartBodyStream
继承了NSInputStream,管理待上传的文件流的读取操作,是作为生成的request的HTTPBodyStream。因为NSInputStream是一个抽象类(abstract class),所以它的子类都必须去重新实现它的方法。AFMultipartBodyStream
主要实现了NSInputStream的的-read:maxLength:
方法,这个方法定义了流的读取方式。 -
AFHTTPBodyPart
管理着单个文件,封装了HTTP上传协议中的每个部分Part,header保存着Content-disposition
和Content-type
数据,相互之间用--boundary
分隔。
AFStreamingMultipartFormData
类的主要属性如下:
/** 需要生成的request */
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;
/** 编码方式 */
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
/** 使用AFCreateMultipartFormBoundary()生成的随机值昨晚Boundary */
@property (readwrite, nonatomic, copy) NSString *boundary;
/** 生成request的bodyStream */
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;
AFStreamingMultipartFormData
实现AFMultipartFormData
协议中添加文件的方法都是通过构建一个新的AFHTTPBodyPart
再添加到bodyStream,再由bodyStream去处理。
AFMultipartBodyStream
类的主要属性如下:
/** 编码方式 */
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
/** 一个存储着AFHTTPBodyPart的数组 */
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;
/** 在流open时,记录HTTPBodyParts的迭代器 */
@property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator;
/** 当前正在读取的AFHTTPBodyPart */
@property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart;
/** 以下两个属性会影响读取速度和所占CUP资源,如果当前处理的事务较多,可以修改这两属性以避免造成界面卡顿。 */
/** 每次读取数据的字节数 */
@property (nonatomic, assign) NSUInteger numberOfBytesInPacket;
/** 每次读取间隔时间 */
@property (nonatomic, assign) NSTimeInterval delay;
AFMultipartBodyStream
主要的功能是从硬盘或者内存中读取数据,代码如下:
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}
NSInteger totalNumberOfBytesRead = 0;
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
if (numberOfBytesRead == -1) {
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
return totalNumberOfBytesRead;
}
由以上代码可以看出,读取的工作分配到各个AFHTTPBodyPart
去完成。
AFHTTPBodyPart
的主要属性:
/** 编码方式 */
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/** 头部描述 */
@property (nonatomic, strong) NSDictionary *headers;
/** boundary标识符 */
@property (nonatomic, copy) NSString *boundary;
/** 上传的对象,可以是NSData、NSURL、NSInputStream */
@property (nonatomic, strong) id body;
/** 文件大小 */
@property (nonatomic, assign) unsigned long long bodyContentLength;
/** 整个流读取过程中最基本的读取文件流 */
@property (nonatomic, strong) NSInputStream *inputStream;
/** 是否是首个文件 */
@property (nonatomic, assign) BOOL hasInitialBoundary;
/** 是否是最后一个文件 */
@property (nonatomic, assign) BOOL hasFinalBoundary;
/** 私有变量 */
/**
AFHTTPBodyPart是分阶段读取的,分别读取boundary部分,header部分和body部分。这实例变量标准着目前阶段。
*/
AFHTTPBodyPartReadPhase _phase;
/** 数据偏移量 */
unsigned long long _phaseReadOffset;
AFHTTPBodyPart
将读取数据分为几个阶段进行,一个阶段读完则进行下一个阶段,主要代码如下:
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
NSInteger totalNumberOfBytesRead = 0;
if (_phase == AFEncapsulationBoundaryPhase) {
NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
if (_phase == AFHeaderPhase) {
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
if (_phase == AFBodyPhase) {
NSInteger numberOfBytesRead = 0;
numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
if (numberOfBytesRead == -1) {
return -1;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
[self transitionToNextPhase];
}
}
}
if (_phase == AFFinalBoundaryPhase) {
NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
return totalNumberOfBytesRead;
}
AFHTTPRequestSerializer子类
AFHTTPRequestSerializer
的子类主要是对参数的格式进行扩展。
AFJSONRequestSerializer
将参数转换成JSON格式再以Content-Type : application/json
的方式生成request。关键代码如下:
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
if (![NSJSONSerialization isValidJSONObject:parameters]) {
if (error) {
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)};
*error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
return nil;
}
AFPropertyListRequestSerializer
将参数转换成plist格式再以Content-Type : application/x-plist
的方式生成request。关键代码如下:
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
}
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error];
AFURLResponseSerialization.h
同样是通过协议定义序列化方法:
/**
AFURLResponseSerialization协议是用于将数据根据服务器返回的详细信息解码成更有效的对象表示(object representation)。回复序列化器(Response serializers)还可以为返回的数据提供校验。
例如:一个JSON response serializer 可能在检测状态码(’2xx’范围内)和Content-Type(‘application/json’)后,将JSON response解码一个对象。
*/
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
/**
根据指定response返回的编码对象。
*/
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
AFHTTPResponseSerializer
实现了AFURLResponseSerialization
协议,因为服务器返回的HTTP报文一般都有明确的数据类型(Content-Type),所以对这些数据的处理具体都在各个子类中实现。AFHTTPResponseSerializer
的属性比较少,只有以下两个:
/**
可接受的状态码。当不为nil时,如果返回的状态码不包含在该set中将在校验过程中导致错误。
*/
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
/**
可接受的MIME类型。当不为nil时,如果返回的Content-Type不包含在该set中将在校验过程中导致错误。
*/
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
此外,AFHTTPResponseSerializer
为所有子类统一了一个校验方法:
/**
校验指定的返回和数据。
在它的基础实现中,这个方法检测了可接受的状态码和数据类型。子类可以添加其他特定的检测。
*/
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
AFURLResponseSerialization.m
对服务器的响应数据序列化相对简单点,主要是按照服务器返回数据的类型使用相应的子类将数据转换成对应的Cocoa对象。主要实现的验证方法如下:
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
实现的子类如下:
AFJSONResponseSerializer
解析JSON数据,关键代码:
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
//通过设置removesKeysWithNullValues来移除Null值
AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
AFXMLParserResponseSerializer
解析XML数据,关键代码:
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
[[NSXMLParser alloc] initWithData:data];
AFPropertyListResponseSerializer
解析plist数据,关键代码:
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil];
id responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
所有子类中,值得详细了解的是AFImageResponseSerializer
,这个用来解析图片数据的类。这个类提供了两个属性可供设置:
/**
当解析图片数据去构造responseImage对象时,使用到scale因数。当scale因数为1.0时,生成图片的size将匹配该图片的像素尺寸。
使用其他scale因数时,将依据size属性去改变图片的size。默认情况下,这个值将被设置为mainScreen的scale值,这会自动拉伸图片去适配retina屏的展示。
*/
@property (nonatomic, assign) CGFloat imageScale;
/**
是否根据压缩格式(例如:PNG或者JPEG)自动解压返回的图片。
在iOS系统中当使用setCompletionBlockWithSuccess:failure:方法时允许这个值将带来有效的绘画性能的提升,因为它允许在后台构造一张位图而不是在主线程。默认为YES。
*/
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
这个类除了跟其他子类一样提供解析返回的数据外,还提供了将返回的图片在后台线程解压的额外功能,具体的解压代码如下:
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
if (!data || [data length] == 0) {
return nil;
}
CGImageRef imageRef = NULL;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
CGDataProviderRelease(dataProvider);
UIImage *image = AFImageWithDataAtScale(data, scale);
if (!imageRef) {
if (image.images || !image) {
return image;
}
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
CGImageRelease(imageRef);
return image;
}
// CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
size_t bytesPerRow = 0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
if (colorSpaceModel == kCGColorSpaceModelRGB) {
uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
#pragma clang diagnostic pop
}
CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
if (!context) {
CGImageRelease(imageRef);
return image;
}
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}
在默认情况下,当使用UIImage
的方法直接创建图片时会在图片需要渲染到屏幕上时再放到主线程上解压,如果在主线程中需要同时解压多个图片将可能会导致卡顿。这时候如果在接收到图片时,先在后台线程中进行解压是比较不错的优化。关于更多iOS图片解压处理的知识可以在这里了解。
Security模块
虽然苹果目前已经要求应用都开启ATS功能(App Transport Security)强制使用HTTPS来做请求以此加强网络传输的安全,但是HTTPS也可能会遭受中间人攻击(使用Charles抓包时就用到了这种技术),幸运的是在Client-Server架构中,可以通过SSL绑定(pinnig)来进一步加强网络安全传输。SSL绑定即是将证书放到APP内,在进行网络请求时使用APP内的证书进行验证。接下来看下AFNetworking是怎样实现的。
AFSecurityPolicy
类的介绍:
/**
AFSecurityPolicy使用固定的x.509证书和安全链接上的公钥来评估服务器的受信任程度。
在你的应用中添加绑定的SSL证书有利于防止“中间人攻击”和其他缺陷。
应用在处理敏感的客户数据或者金融信息时,强烈建议在HTTPS的基础上使用SSL绑定的配置和授权来进行网络通讯。
*/
@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>
一些主要的属性:
/**
评估服务器是否受信任的标准。默认是AFSSLPinningModeNone
*/
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
/**
根据SSLPinningMode被用于评估服务器信任的证书。
在你使用AFNetworking作为嵌入framework时,默认情况下,将会把target的AFNetworking.framework内所有.cer证书都添加到该属性内,默认没有证书。使用certificatesInBundle方法从你的target中加载所有证书,再使用policyWithPinningMode:withPinnedCertificates创建新的policy。
注意,如果SSLPinningMode不为AFSSLPinningModeNone,在绑定证书匹配的情况下,evaluateServerTrust:forDomain:将返回true。
*/
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
/**
是否信任无效的或者过期的证书,只在SSLPinningMode为AFSSLPinningModeNone有效。默认为NO。
*/
@property (nonatomic, assign) BOOL allowInvalidCertificates;
/**
是否校验服务器域名。默认为YES。
*/
@property (nonatomic, assign) BOOL validatesDomainName;
验证服务器证书的方法:
/**
评估指定的服务器信任(server trust)在当前安全策略下是否可被信任。
当服务器返回一个鉴权查询(authentication challenge)时需要使用该方法。
@param serverTrust 服务器信任的X.509证书。
@param domain 域名。
@return 是否信任服务器。
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(nullable NSString *)domain;
校验证书都是通过调用系统库<Security/Security.h>
的方法。AFNetworking
所做的操作主要是根据设置的AFSecurityPolicy
对象的属性进行证书验证。当SSLPinningMode
不进行设置或设置为AFSSLPinningModeNone
时,将不进行验证,设置为AFSSLPinningModeCertificate
会使用证书进行验证,设置为AFSSLPinningModePublicKey
将直接使用证书里面的公钥进行验证。可以通过设置pinnedCertificates
属性来设置验证所用的证书,也可以通过+certificatesInBundle:
方法加载单独放在一个Bundle里的证书,如果不设置,AFNetworking
会使用NSBundle
的+bundleForClass:
方法将放在AFNetworking.framework
里面的cer文件作为验证所用证书。具体验证代码如下:
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
值得注意的是,在AFSecurityPolicy.m文件中可以看到使用了类似__Require_Quiet
的宏来通过goto语句来优雅地复用_out块里面的代码,如下:
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
Reachability模块
AFNetworkReachabilityManager类的说明
/**
AFNetworkReachabilityManager用于监听域名或者IP地址的可达性,包括WWAN和WiFi网络接口。
Reachability可以被用来确定一个网络操作失败的后台信息,或者当连接建立时触发网络操作重试。
它不应该用于阻止用户发起网络请求,因为可能需要第一个请求来建立可达性。
*/
@interface AFNetworkReachabilityManager : NSObject
AFNetworkReachabilityManager
主要通过系统库的方法来实现网络监听,并以Block的方式进行回调,主要实现的代码如下:
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}
上面代码中使用新建的callback而不是使用networkReachabilityStatusBlock
属性是为了将调用-startMonitoring
和-setReachabilityStatusChangeBlock
分开,在使用的过程中,我们可以随时重新设置回调而不会受到影响。
还有另外关键的一点,AFNetworking
使用__strong __typeof(weakSelf)strongSelf = weakSelf;
在block中重新持有了对象,这样做的原因是既可以保证在block范围内对象始终存在,又不会引起循环引用。因为strongSelf的作用域只在本block内,对象也不直接持有该block,所以当block被释放时,strongSelf也会被释放。
NSURLSession模块
NSURLSession
是用于处理HTTP请求的类,通过一个NSURLSessionConfiguration
对象配置处理Cache、Cookies、Credibility的方式,并通过一系列协议来对请求进行处理,结构如下:
NSURLSession
可以根据配置创建一个个NSURLSessionTask
对象来完成每一次HTTP请求任务,NSURLSessionTask
就是一次HTTP请求和响应的交互过程的封装。
AFURLSessionManager
AFNetworking
通过AFURLSessionManager
来对NSURLSession
进行封装管理。AFURLSessionManager
简化了用户网络请求的操作,使得用户只需以block的方式来更简便地进行网络请求操作,而无需实现类似NSURLSessionDelegate
、NSURLSessionTaskDelegate
等协议。用户只需使用到NSURLSessionTask
的-resume
、-cancel
和-suspned
等操作,以及在block中定义你需要的操作就可以。
AFURLSessionManager.h
/**
AFURLSessionManager创建并管理一个基于NSURLSessionConfiguration对象的对象,并且遵守了<NSURLSessionTaskDelegate>、<NSURLSessionDataDelegate>、<NSURLSessionDownloadDelegate>以及<NSURLSessionDelegate>协议。
*/
@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
//属性
/** 管理的session */
@property (readonly, nonatomic, strong) NSURLSession *session;
/** 代理回调中在运行的operationQueue。 */
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
/**
使用方法dataTaskWithRequest:success:failure:并且使用了GET、POST等简便方法的返回的数据将被自动使用responseSerializer检验和序列化。默认为一个AFJSONResponseSerializer对象。
*/
@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
/** 用于评估服务器受信任情况的安全策略。默认使用defaultPolicy。 */
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
/** 网络监测管理对象。默认sharedManager。 */
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
/** 当前运行在session中的所有数据、上传和下载任务。 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;
/** 执行completionBlock的队列。默认为主线程队列。 */
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
/** 执行completionBlock的group。默认为一个私有的dispatch_group。 */
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
//方法
/** 根据指定的配置创建并返回一个NSURLSession。 */
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
/** 一系列类似以下的创建NSURLSessionTask的方法。 */
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler DEPRECATED_ATTRIBUTE;
/** 一系列类似以下设置回调的方法。 */
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
AFURLSessionManager
将每一个task都分别交给一个AFURLSessionManagerTaskDelegate
对象进行管理,AFURLSessionManagerTaskDelegate
相当于扩展了NSURLSessionTask
的属性。
NSURLSessionTask属性
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
- (instancetype)initWithTask:(NSURLSessionTask *)task;
@property (nonatomic, weak) AFURLSessionManager *manager;
/** 服务器返回响应的数据 */
@property (nonatomic, strong) NSMutableData *mutableData;
/** 上传和下载进度条 */
@property (nonatomic, strong) NSProgress *uploadProgress;
@property (nonatomic, strong) NSProgress *downloadProgress;
@property (nonatomic, copy) NSURL *downloadFileURL;
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
@end
AFURLSessionManagerTaskDelegate
通过实现NSURLSessionTaskDelegate
、NSURLSessionDataDelegate
和NSURLSessionDownloadDelegate
协议的相关方法来监听网络请求的完整过程,并操作它的属性值mutableData、uploadProgress等,并在对应时刻回调block。
以一个下载任务为例,关键代码如下:
/**
监听NSProgress的fractionCompleted属性,fractionCompleted记录着进度条的完成比例。
*/
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
---------------------------------------------------
/**
当fractionCompleted发生变化时进行回调。
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
---------------------------------------------------
/**
监听NSURLSessionDownloadTask的进度,修改NSProgress的totalUnitCount和completedUnitCount进而影响fractionCompleted属性。
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
self.downloadProgress.completedUnitCount = totalBytesWritten;
}
---------------------------------------------------
/**
下载完成后,将文件保存在downloadTaskDidFinishDownloading返回的URL。
*/
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
为了在任务的暂停和恢复时发送通知,AFNetworking
使用了动态swizzling修改了系统的-resume
和-suspend
方法的IMP,当调用-resume
时发送通知,关键代码如下:
/* 遍历NSURLSessionDataTask类及其父类的resume和suspend方法 */
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
/* 交换IMP */
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
AFURLSessionManager
通过一个字典mutableTaskDelegatesKeyedByTaskIdentifier
来保存用户创建的task和其对应的AFURLSessionManagerTaskDelegate
对象,因为NSMutableDictionary
对象会将其key值进行copy和对其value值进行强引用,所以无需再持有task和delegate。AFURLSessionManager
工作流程是:
- 创建一个task和一个delegate来管理task,并将它们保存到字典里
- 实现
NSURLSessionDelegate
等协议的方法,监听任务状态,通过block回调 - 当任务完成时,移除task和delegate
以下载任务为例,关键代码如下:
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
__block NSURLSessionDownloadTask *downloadTask = nil;
/* 在一个串行队列创建task */
url_session_manager_create_task_safely(^{
downloadTask = [self.session downloadTaskWithRequest:request];
});
/* 创建delegate管理task并将它们加入字典 */
[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
return downloadTask;
}
---------------------------------------------------
/* 创建delegate管理task并将它们加入字典 */
- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
if (destination) {
delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
return destination(location, task.response);
};
}
downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
/* 加入字典 */
[self setDelegate:delegate forTask:downloadTask];
delegate.downloadProgressBlock = downloadProgressBlock;
}
---------------------------------------------------
/* 字典保存task和delegate,使用锁保证线程安全 */
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
---------------------------------------------------
/* 任务完成后移除出字典 */
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
一些其他值得注意的代码:
/* 服务器返回鉴权查询(authentication challenge)时,使用securityPolicy进行信任评估 */
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
/* 通过信号量实现数据的同步返回 */
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
AFHTTPSessionManager
AFHTTPSessionManager
是AFURLSessionManager
的子类,它的主要是根据HTTP的请求方式get、post、head、delete进行进一步的封装以便我们更方便地使用。以POST请求方式为例,看下AFHTTPSessionManager
如何进行网络请求:
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
Reference
https://segmentfault.com/a/1190000005833523
https://www.raywenderlich.com/158106/urlsession-tutorial-getting-started
https://developer.apple.com/documentation/foundation/nsurlsession?language=occ
https://developer.apple.com/documentation/foundation/url_loading_system?language=objc
本文作者:西瓜冰soso
本文链接:https://www.jianshu.com/p/8deccb9c8398
温馨提示:
由于本文是原创文章,可能会有更新以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导。另外文章如有错误,请不吝指教,谢谢。