AFNetworking学习笔记与实践

自从AF使用NSURLSession重写之后,还没有看过源码,NSURLSession是基于NSURLConnection的封装,应该是汲取了AF之前的设计,内部封装了一个NSOperation来实现异步请求。最近要做一个音频播放的边播边缓存的功能,网路请求这块需要封装一下,随便研究一下AF的源码,这里我按模块逐个分析一下。

AFURLSessionManager

这个模块是AF中最大也是最重要的一个模块,它封装了一个session,提供了一系列的初始化方法和创建task的方法。如果大家对session和task的概念还不太理解,可以先看一下NSURLSession这个类的相关内容(这个大概说一下,session是管理所有网络请求的,每一个网络请求是一个task,task是使用session创建的,所有task共享session的相关设置)。

先来看一下创建task的方法,这里总共有datatask,uploadtask和downloadtask三种task,不过他们的创建逻辑都是一样的,以datatask为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}

先看看上面这个宏的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}

这里这样做主要是为了解决ios8之前的一个bug,如果异步同时创建task的话,task的id有可能是一样的,这会导致回调时出现问题(completionHandler被替换),为了解决这个问题,创建task的操作都放在一个串行队列中执行。

然后看一下添加代理这个又是要干嘛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}

可以看到,af为每一个task都创建了一个delegate,然后将代理存在了self.mutableTaskDelegatesKeyedByTaskIdentifier这个字典中,键值是task的id,这里也解释了上面的为啥要同步创建task,那么它为啥要为每个task添加一个代理呢,我们继续往下看。

看完task的创建,然后我们再来看一下数据的回调。af为每个NSURLSession的回调函数创建了对应block和setBlock的方法,如果你需要哪个代理里面的数据,直接创建对应的block即可。

我们先来看最常用的回调方法,即请求完成的回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- (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];
//将该task的代理从字典中移除
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}

咋一看有点奇怪,这2方法名一毛一样啊。没有错,这两个方法是同一个代理方法,只是他们的实现对象是不一样的,第一个的实现对象是AFURLSessionManager类,第二个的实现对象则是AFURLSessionManagerTaskDelegate这个类,这里就解释上面的为啥要为每一个task设置一个代理,af这里其实是吧NSURLSession的代理又包了一层,将NSURLSession的数据都传到AFURLSessionManagerTaskDelegate这个类里面去处理了。

然后我们看一下这个回调中对数据的处理,可以看到回调的返回都是放在一个gcd的group中处理的,如果我们设置了返回处理线程的话就使用设置的线程,否则会在主线程中处理数据的返回,如果有错误是直接返回了,没有错误的话,会先使用responeseSerializer去验证一下返回的数据格式是否合法,这个类我们下面在细看。

继续看其他几个我觉得比较常用的回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
if (self.dataTaskDidReceiveResponse) {
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
if (completionHandler) {
completionHandler(disposition);
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
//这里保存的数据会在complete的回调中使用
[self.mutableData appendData:data];
}

这2个回调一个是收到responese被触发,这个回调可以拿到相应头部信息,比如接收文件的大小,格式等,一个是收到数据时被触发,进度相关的信息就在通过这个回调拿到的。还有一点就是,download的task不会走datatask的代理,意味着download的数据只能在下载完成后通过文件的方式拿到。

AFHTTPSessionManager

这个类就是继承了AFURLSessionManager,然后提供了一些封装好的创建task的方法。平时二次封装也是使用这个类就行。

然后这个类有三个比较重要的属性

1
2
3
4
5
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;

这里简单介绍每个属性的用途,下面还有详解。
requestSerializer主要用来设置httpheader,responseSerializer主要用来解析返回数据是否合法,
securityPolicy主要用来验证服务器证书。

AFHTTPRequestSerializer

先来看一下AFHTTPRequestSerializer在AFHTTPSessionManager中是如何使用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
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;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__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;
}

可以看到,在调用AFURLSessionManager的创建task方法前,会先调用一下下面这个方法生成一个request

1
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

那我们来具体看一下这个方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
- (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;
}
- (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和method创建一个mutableRequest,
  • kvo相关属性,如果不为空则kvc到mutableRequest
  • 设置HTTPRequestHeaders数组中相关属性到mutableRequest
  • 将传入的参数做一下url编码
  • 如果是get,head,delete方式,将参数加到url后面,否则,将参数设置为mutableRequest的body,同时设置mutableRequest的Content-Type.

可以看出,这个类的主要作用就是创建request,给request设置相应的header和body,对url和参数进行编码(url使用URLEncode,body使用UNICode编码).

AFHTTPResponseSerializer

这个类在上面的complete回调中有提到,这里具体分析。

af一共提供了7种ResponseSerializer,分别是:

1
2
3
4
5
6
7
8
9
10
11
12
13
AFHTTPResponseSerializer
AFJSONResponseSerializer
AFXMLParserResponseSerializer
AFXMLDocumentResponseSerializer
AFPropertyListResponseSerializer
AFImageResponseSerializer
AFCompoundResponseSerializer

不同的response针对不同的数据类型。

他们的主要区别其实就在acceptableContentTypes这个属性上,它是一个集合,用来存储我们需要接受的数据类型。

比如JSON的acceptableContentTypes为:

1
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];

IMAGE的acceptableContentTypes为:

1
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil];

我们可以通过NSHTTPSessionManager的responseSerializer修改你需要的数据类型。

回到之前的complete回调,看看具体怎么使用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
- (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;
}

[response MIMEType] 这个type就是当前服务器返回的数据的格式,需要self.acceptableContentTypes这个集合中包含上面的type,解析数据的时候才会返回成功,所以,如果遇到解析失败的话,可以断点到这里看一下这个type然后手动添加到acceptableContentTypes这个集合中。

AFSecurityPolicy

af提供了三种验证证书的方式,第一种不需要本地证书。

1
2
3
4
5
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,//默认方式,只需要验证服务器返回的证书是否合法
AFSSLPinningModePublicKey,//需要验证本地证书中的公钥是否包含服务器返回的证书中的公钥
AFSSLPinningModeCertificate,//需要验证本地证书是否在服务器证书的证书链中
};

对应的有几种不同的初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
return [NSSet setWithSet:certificates];
}
+ (NSSet *)defaultPinnedCertificates {
static NSSet *_defaultPinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
_defaultPinnedCertificates = [self certificatesInBundle:bundle];
});
return _defaultPinnedCertificates;
}
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
[securityPolicy setPinnedCertificates:pinnedCertificates];
return securityPolicy;
}

然后我们看一下到底是如何进行证书验证的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
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;
}
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}

这里重点看一下self.allowInvalidCertificates这个参数,可以看到,如果使用默认的验证策略,同时self.allowInvalidCertificates = YES的话,会直接返回YES,相当于没有验证,所有这里我们一般将self.allowInvalidCertificates = NO。self.validatesDomainName这个参数默认为YES就可以了。

验证服务器证书是否合法的方法为AFServerTrustIsValid,这个方法里面就是调用的系统的方法。

那这个验证方法到底在哪里调用的呢,我们看一下下面这个代理,在NSURLSessionManager中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (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);
}
}

所有的https请求都会先进这个代理,这个challenge.protectionSpace对象中就是服务器发过来的需要客户端验证的证书,域名等相关重要信息,然后af调用evaluateServerTrust这个方法对相关信息进行验证。

实战,简单的二次封装

我这边使用一个单例来管理AFHTTPSessionManager,在init方法中,设置相关delegate对应的block.这里设置block主要是满足我需要在开始获取到音频数据的时候就对数据进行处理,不需要等到整个请求完成再处理数据的需求。如果需要其他delegate的信息,添加相应的block即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
+ (instancetype)shareInstance{
static LLYHttpSessionManager *_llyHttpSessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_llyHttpSessionManager = [[LLYHttpSessionManager alloc]init];
});
return _llyHttpSessionManager;
}
- (instancetype)init{
self = [super init];
if (self) {
_httpSessionManager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
_httpSessionManager.securityPolicy = [AFSecurityPolicy defaultPolicy];
// [_httpSessionManager.requestSerializer setValue:[self generateXUserAgent] forHTTPHeaderField:@"User-Agent"];
// [_httpSessionManager.requestSerializer setValue:[self generateClientCookie] forHTTPHeaderField:@"Cookie"];
__weak __typeof(self)weakSelf = self;
[_httpSessionManager setDataTaskDidReceiveResponseBlock:^NSURLSessionResponseDisposition(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSURLResponse * _Nonnull response) {
if (weakSelf.didReceiveResponseBlock) {
weakSelf.didReceiveResponseBlock(session, dataTask, response);
}
return NSURLSessionResponseAllow;
}];
[_httpSessionManager setDataTaskDidReceiveDataBlock:^(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSData * _Nonnull data) {
if (weakSelf.didReceiveDataBlock) {
weakSelf.didReceiveDataBlock(session, dataTask, data);
}
}];
//默认json
self.fileType = LLYHttpFileType_JSON;
}
return self;
}

因为我这边需要请求音视频的数据,af提供的几种responese默认是不支持音视频格式的,所有我封装了几种文件type,每种type对应不同的responese

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typedef NS_ENUM(NSUInteger, LLYHttpFileType) {
LLYHttpFileType_JSON = 0,
LLYHttpFileType_IMAGE,
LLYHttpFileType_AUDIO,
LLYHttpFileType_VIDEO
};
- (void)setResponseSerializer:(LLYHttpFileType)fileType{
switch (fileType) {
case LLYHttpFileType_JSON:{
_httpSessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
}
break;
case LLYHttpFileType_IMAGE:
{
_httpSessionManager.responseSerializer = [AFImageResponseSerializer serializer];
}
break;
case LLYHttpFileType_AUDIO:{
_httpSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
_httpSessionManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"audio/mpeg",nil];
}
break;
case LLYHttpFileType_VIDEO:{
_httpSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
_httpSessionManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"video/mp4",@"video/mpeg",nil];
}
break;
default:
{
_httpSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
}
break;
}
}

然后提供了2个block,分别是didReceiveResponseBlock和didReceiveDataBlock,如果需要拿到请求过程中的相关数据和信息,直接实现这2个block就ok了.

1
2
@property (nonatomic, copy) DidReceiveResponseBlock didReceiveResponseBlock;
@property (nonatomic, copy) DidReceiveDataBlock didReceiveDataBlock;

针对不同的数据类型,我这边提供了相应的task的创建方法,然后还提供了一个总的创建task的方法和一个下载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- (nullable NSURLSessionTask *)requestJSONWithMethod:(LLYHttpMethod)method
urlString:(nullable NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress * _Nullable downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask * _Nullable task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
- (nullable NSURLSessionTask *)requestIMAGEWithMethod:(LLYHttpMethod)method
urlString:(nullable NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress * _Nullable downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask * _Nullable task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
- (nullable NSURLSessionTask *)requestAUDIOWithMethod:(LLYHttpMethod)method
urlString:(nullable NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress * _Nullable downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask * _Nullable task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
- (nullable NSURLSessionTask *)requestVIDEOWithMethod:(LLYHttpMethod)method
urlString:(nullable NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress * _Nullable downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask * _Nullable task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure;
- (nullable NSURLSessionTask *)requestWithMethod:(LLYHttpMethod)method
fileType:(LLYHttpFileType)fileType
urlString:(nullable NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress * _Nullable downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask * _Nullable task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError * _Nullable error))failure
;
- (nullable NSURLSessionDownloadTask *)downloadWithUrl:(nullable NSString *)urlString
progress:(nullable void (^)(NSProgress * _Nullable downloadProgress))downloadProgressBlock
destination:(nullable NSURL * _Nullable(^)(NSURL * _Nullable targetPath, NSURLResponse * _Nullable response))destination
completionHandler:(nullable void (^)(NSURLResponse * _Nullable response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;

实现部分就比较简单了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
- (nullable NSURLSessionTask *)requestWithMethod:(LLYHttpMethod)method
fileType:(LLYHttpFileType)fileType
urlString:(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
{
self.fileType = fileType;
NSURLSessionTask *task = nil;
switch (method) {
case LLYHttpMethod_GET:
{
task = [_httpSessionManager GET:URLString parameters:parameters progress:downloadProgress success:success failure:failure];
}
break;
case LLYHttpMethod_POST:
{
task = [_httpSessionManager POST:URLString parameters:parameters progress:downloadProgress success:success failure:failure];
}
break;
case LLYHttpMethod_HEAD:
{
task = [_httpSessionManager HEAD:URLString parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task) {
if (success) {
success(task,nil);
}
} failure:failure];
}
break;
default:
break;
}
return task;
}
- (NSURLSessionDownloadTask *)downloadWithUrl:(NSString *)urlString
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler{
NSURLSessionDownloadTask *task = nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
task = [_httpSessionManager downloadTaskWithRequest:request progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
[task resume];
return task;
}

我的demo