版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.02.28 |
前言
我们做APP发起网络请求,都离不开一个非常有用的框架AFNetworking,可以说这个框架的知名度已经超过了苹果的底层网络请求部分,很多人可能不知道苹果底层是如何发起网络请求的,但是一定知道
AFNetworking
,接下来几篇我们就一起详细的解析一下这个框架。感兴趣的可以看上面写的几篇。
1. AFNetworking源码探究(一) —— 基本介绍
2. AFNetworking源码探究(二) —— GET请求实现之NSURLSessionDataTask实例化(一)
3. AFNetworking源码探究(三) —— GET请求实现之任务进度设置和通知监听(一)
4. AFNetworking源码探究(四) —— GET请求实现之代理转发思想(一)
回顾
上一篇主要介绍了代理的转发思想,NSURLSession的代理在类AFURLSessionManager
中实现,转发到AFURLSessionManagerTaskDelegate
自定义的代理中。从这一篇开始会用几篇介绍这个NSURLSession的代理的用法。
1. - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;
先看代理方法
/* The last message a session receives. A session will only become
* invalid because of a systemic error or when it has been
* explicitly invalidated, in which case the error parameter will be nil.
*/
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;
下面是代理方法的实现
@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
如果你使用finishTasksAndInvalidate
函数使该session失效,
那么session首先会先完成最后一个task,然后再调用URLSession:didBecomeInvalidWithError:
代理方法,
如果你调用invalidateAndCancel
方法来使session失效,那么该session会立即调用上面的代理方法。
这个就是该代理方法调用的时机。
这个代理方法做了两个事情:
- 调用了自定义的
block - sessionDidBecomeInvalid
- 发送了一个通知,但是没有对象去监听,可能是作者故意留的接口吧,这个通知目前是没有用到。
2. - (void)URLSession:(NSURLSession *)sessiondidReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
我们先看一下系统定义的API
/* If implemented, when a connection level authentication challenge
* has occurred, this delegate will be given the opportunity to
* provide authentication credentials to the underlying
* connection. Some types of authentication will apply to more than
* one request on a given connection to a server (SSL Server Trust
* challenges). If this delegate message is not implemented, the
* behavior will be to use the default handling, which may involve user
* interaction.
*/
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
下面看一下在该类中的实现。
- (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 {
// 此处服务器要求客户端的接收认证挑战方法是NSURLAuthenticationMethodServerTrust
// 也就是说服务器端需要客户端返回一个根据认证挑战的保护空间提供的信任(即challenge.protectionSpace.serverTrust)产生的挑战证书。
// 而这个证书就需要使用credentialForTrust:来创建一个NSURLCredential对象
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)
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认证,服务器向客户端进行质询和挑战。这个类似的其实在SDWebImage
中讲过,这里也简单的看一下。
web服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战(NSURLAuthenticationChallenge *challenge)
。接收到挑战后,客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition
和NSURLCredential *credential
(disposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust
的时候,才需要生成挑战证书)。最后调用completionHandler回应服务器端的挑战。
当某个session使用SSL/TLS
协议,第一次和服务器端建立连接的时候,服务器会发送给iOS客户端一个证书,此方法允许你的app验证服务期端的证书链(certificate keychain)
注:如果你没有实现该方法,该session会调用其NSURLSessionTaskDelegate
的代理方法URLSession:task:didReceiveChallenge:completionHandler:
。
3. - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
先看一下系统的API。
/* If an application has received an
* -application:handleEventsForBackgroundURLSession:completionHandler:
* message, the session delegate will receive this message to indicate
* that all messages previously enqueued for this session have been
* delivered. At this time it is safe to invoke the previously stored
* completion handler, or to begin any internal updates that will
* result in invoking the completion handler.
*/
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session API_AVAILABLE(ios(7.0), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
下面看一下AFN中的实现
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
typedef void (^AFURLSessionDidFinishEventsForBackgroundURLSessionBlock)(NSURLSession *session);
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didFinishEventsForBackgroundURLSession(session);
});
}
}
- 当session中所有已经入队的消息被发送出去后,会调用该代理方法。
- 在iOS中,当一个后台传输任务完成或者后台传输时需要证书,而此时你的app正在后台挂起,那么你的app在后台会自动重新启动运行,并且这个app的
UIApplicationDelegate
会发送一个application:handleEventsForBackgroundURLSession:completionHandler:
消息。该消息包含了对应后台的session的identifier,而且这个消息会导致你的app启动。你的app随后应该先存储completion handler,然后再使用相同的identifier创建一个background configuration,并根据这个background configuration
创建一个新的session。这个新创建的session会自动与后台任务重新关联在一起。 - 当你的app获取了一个
URLSessionDidFinishEventsForBackgroundURLSession:
消息,这就意味着之前这个session中已经入队的所有消息都转发出去了,这时候再调用先前存取的completion handler是安全的,或者因为内部更新而导致调用completion handler
也是安全的。
参考文章
后记
本篇主要介绍了
NSURLSessionDelegate
的三个代理方法。介绍了方法的使用场景和注意事项。