版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.03.06 |
前言
我们做APP发起网络请求,都离不开一个非常有用的框架AFNetworking,可以说这个框架的知名度已经超过了苹果的底层网络请求部分,很多人可能不知道苹果底层是如何发起网络请求的,但是一定知道
AFNetworking
,接下来几篇我们就一起详细的解析一下这个框架。感兴趣的可以看上面写的几篇。
1. AFNetworking源码探究(一) —— 基本介绍
2. AFNetworking源码探究(二) —— GET请求实现之NSURLSessionDataTask实例化(一)
3. AFNetworking源码探究(三) —— GET请求实现之任务进度设置和通知监听(一)
4. AFNetworking源码探究(四) —— GET请求实现之代理转发思想(一)
5. AFNetworking源码探究(五) —— AFURLSessionManager中NSURLSessionDelegate详细解析(一)
6. AFNetworking源码探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate详细解析(一)
7. AFNetworking源码探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate详细解析(一)
8. AFNetworking源码探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate详细解析(一)
9. AFNetworking源码探究(九) —— AFURLSessionManagerTaskDelegate中三个转发代理方法详细解析(一)
10. AFNetworking源码探究(十) —— 数据解析之数据解析架构的分析(一)
11. AFNetworking源码探究(十一) —— 数据解析之子类中协议方法的实现(二)
12. AFNetworking源码探究(十二) —— 数据解析之子类中协议方法的实现(三)
13. AFNetworking源码探究(十三) —— AFSecurityPolicy与安全认证 (一)
14. AFNetworking源码探究(十四) —— AFSecurityPolicy与安全认证 (二)
15. AFNetworking源码探究(十五) —— 请求序列化之架构分析(一)
16. AFNetworking源码探究(十六) —— 请求序列化之协议方法的实现(二)
17. AFNetworking源码探究(十七) —— _AFURLSessionTaskSwizzling实现方法交换(转载)(一)
18. AFNetworking源码探究(十八) —— UIKit相关之AFNetworkActivityIndicatorManager(一)
19. AFNetworking源码探究(十九) —— UIKit相关之几个分类(二)
20. AFNetworking源码探究(二十) —— UIKit相关之AFImageDownloader图像下载(三)
21. AFNetworking源码探究(二十一) —— UIKit相关之UIImageView+AFNetworking分类(四)
22. AFNetworking源码探究(二十二) —— UIKit相关之UIButton+AFNetworking分类(五)
回顾
上一篇讲述了UIButton+AFNetworking的UIButton的一个分类。分析了其下载器的下载、图像的下载以及背景图像的下载。这一篇就继续讲述AFN中UIWebView的分类。
接口API
我们看一下UIWebView分类的API接口。
/**
This category adds methods to the UIKit framework's `UIWebView` class. The methods in this category provide increased control over the request cycle, including progress monitoring and success / failure handling.
@discussion When using these category methods, make sure to assign `delegate` for the web view, which implements `–webView:shouldStartLoadWithRequest:navigationType:` appropriately. This allows for tapped links to be loaded through AFNetworking, and can ensure that `canGoBack` & `canGoForward` update their values correctly.
*/
@interface UIWebView (AFNetworking)
/**
The session manager used to download all requests.
*/
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
/**
Asynchronously loads the specified request.
// 异步加载指定的请求
@param request A URL request identifying the location of the content to load. This must not be `nil`.
@param progress A progress object monitoring the current download progress.
@param success A block object to be executed when the request finishes loading successfully. This block returns the HTML string to be loaded by the web view, and takes two arguments: the response, and the response string.
@param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred.
*/
- (void)loadRequest:(NSURLRequest *)request
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(nullable void (^)(NSError *error))failure;
/**
Asynchronously loads the data associated with a particular request with a specified MIME type and text encoding.
// 异步加载具有指定MIME类型和文本编码格式的指定请求的数据
@param request A URL request identifying the location of the content to load. This must not be `nil`.
@param MIMEType The MIME type of the content. Defaults to the content type of the response if not specified.
@param textEncodingName The IANA encoding name, as in `utf-8` or `utf-16`. Defaults to the response text encoding if not specified.
@param progress A progress object monitoring the current download progress.
@param success A block object to be executed when the request finishes loading successfully. This block returns the data to be loaded by the web view and takes two arguments: the response, and the downloaded data.
@param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred.
*/
- (void)loadRequest:(NSURLRequest *)request
MIMEType:(nullable NSString *)MIMEType
textEncodingName:(nullable NSString *)textEncodingName
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(nullable void (^)(NSError *error))failure;
@end
这里接口有一个属性和两个方法。
该类为UIKit框架的UIWebView
类添加方法。 此类别中的方法可以更好地控制请求周期,包括进度监视和成功/失败处理。
在使用这些类别方法时,请确保为webView分配delegate
,它适当地实现- webView:shouldStartLoadWithRequest:navigationType:
。 这允许通过AFNetworking加载引出的链接,并且可以确保canGoBack
和canGoForward
正确地更新它们的值。
获取数据任务
这里面实现了UIWebView
的另外一个分类_AFNetworking
,利用runtime获取了数据任务。
- (NSURLSessionDataTask *)af_URLSessionTask {
return (NSURLSessionDataTask *)objc_getAssociatedObject(self, @selector(af_URLSessionTask));
}
- (void)af_setURLSessionTask:(NSURLSessionDataTask *)af_URLSessionTask {
objc_setAssociatedObject(self, @selector(af_URLSessionTask), af_URLSessionTask, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
获取AFHTTPSessionManager和AFHTTPResponseSerializer对象
这个是在UIWebView
的分类AFNetworking
中实现的,实现方式还是使用runtime。
// AFHTTPSessionManager对象的获取
- (AFHTTPSessionManager *)sessionManager {
static AFHTTPSessionManager *_af_defaultHTTPSessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultHTTPSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
_af_defaultHTTPSessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];
_af_defaultHTTPSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
});
return objc_getAssociatedObject(self, @selector(sessionManager)) ?: _af_defaultHTTPSessionManager;
}
- (void)setSessionManager:(AFHTTPSessionManager *)sessionManager {
objc_setAssociatedObject(self, @selector(sessionManager), sessionManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// AFHTTPResponseSerializer对象的实现
- (AFHTTPResponseSerializer *)responseSerializer {
static AFHTTPResponseSerializer *_af_defaultResponseSerializer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultResponseSerializer = [AFHTTPResponseSerializer serializer];
});
return objc_getAssociatedObject(self, @selector(responseSerializer)) ?: _af_defaultResponseSerializer;
}
- (void)setResponseSerializer:(AFHTTPResponseSerializer *)responseSerializer {
objc_setAssociatedObject(self, @selector(responseSerializer), responseSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
请求数据的实现
主要就是下面两个方法。
- (void)loadRequest:(NSURLRequest *)request
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(nullable void (^)(NSError *error))failure;
- (void)loadRequest:(NSURLRequest *)request
MIMEType:(nullable NSString *)MIMEType
textEncodingName:(nullable NSString *)textEncodingName
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(nullable void (^)(NSError *error))failure;
其实看一下源码就知道,上面方法是通过调用下面的方法实现的,传递的参数MIMEType和textEncodingName都为nil,并在数据回来中进行了处理。下面我们就一起看一下。
1. 加载指定请求
主要看一下实现
- (void)loadRequest:(NSURLRequest *)request
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(void (^)(NSError *error))failure
{
[self loadRequest:request MIMEType:nil textEncodingName:nil progress:progress success:^NSData *(NSHTTPURLResponse *response, NSData *data) {
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (encoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
}
}
NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding];
if (success) {
string = success(response, string);
}
return [string dataUsingEncoding:stringEncoding];
} failure:failure];
}
我们看一下在成功回调做的处理。
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (encoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
}
}
NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding];
if (success) {
string = success(response, string);
}
return [string dataUsingEncoding:stringEncoding];
这里首先获取编码格式,默认是NSUTF8StringEncoding
,如果response.textEncodingName
存在,那么就进行相关编码转化,最后就是利用生成的编码格式,生成NSString类型的数据,并作为成功回调的参数进行传递。
2. 加载指定MIME类型、编码格式的请求
下面就是看一下请求
- (void)loadRequest:(NSURLRequest *)request
MIMEType:(NSString *)MIMEType
textEncodingName:(NSString *)textEncodingName
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(void (^)(NSError *error))failure
{
NSParameterAssert(request);
if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
[self.af_URLSessionTask cancel];
}
self.af_URLSessionTask = nil;
__weak __typeof(self)weakSelf = self;
__block NSURLSessionDataTask *dataTask;
dataTask = [self.sessionManager
dataTaskWithRequest:request
uploadProgress:nil
downloadProgress:nil
completionHandler:^(NSURLResponse * _Nonnull response, id _Nonnull responseObject, NSError * _Nullable error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (error) {
if (failure) {
failure(error);
}
} else {
if (success) {
success((NSHTTPURLResponse *)response, responseObject);
}
[strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];
if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
[strongSelf.delegate webViewDidFinishLoad:strongSelf];
}
}
}];
self.af_URLSessionTask = dataTask;
if (progress != nil) {
*progress = [self.sessionManager downloadProgressForTask:dataTask];
}
[self.af_URLSessionTask resume];
if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.delegate webViewDidStartLoad:self];
}
}
这个实现主要做了下面几个工作:
- 任务状态的判断及逻辑处理
- AFHTTPSessionManager对象开启指定request的请求,并处理成功和失败的回调
- 处理进度,重新开启任务
(a) 任务状态的判断及逻辑处理
主要对应下面这段代码
NSParameterAssert(request);
if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
[self.af_URLSessionTask cancel];
}
self.af_URLSessionTask = nil;
这里首选使用断言NSParameterAssert
进行参数判断,参数为空就崩溃。然后判断任务的状态,如果任务正在进行或者暂停,那么就取消该任务。并将任务指针设置为nil。
(b) 开启指定request的请求
主要对应下面这段代码。
__weak __typeof(self)weakSelf = self;
__block NSURLSessionDataTask *dataTask;
dataTask = [self.sessionManager
dataTaskWithRequest:request
uploadProgress:nil
downloadProgress:nil
completionHandler:^(NSURLResponse * _Nonnull response, id _Nonnull responseObject, NSError * _Nullable error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (error) {
if (failure) {
failure(error);
}
} else {
if (success) {
success((NSHTTPURLResponse *)response, responseObject);
}
[strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];
if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
[strongSelf.delegate webViewDidFinishLoad:strongSelf];
}
}
}];
self.af_URLSessionTask = dataTask;
这里逻辑也是很清晰了吧,如果存在错误,那么就回调failure(error)
,否则就说明没有失败,那么就进行回调success((NSHTTPURLResponse *)response, responseObject)
。
[strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];
if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
[strongSelf.delegate webViewDidFinishLoad:strongSelf];
}
接着就是利用上面方法请求数据,并设置了代理方法。
(c) 处理进度,重新开启任务
if (progress != nil) {
*progress = [self.sessionManager downloadProgressForTask:dataTask];
}
[self.af_URLSessionTask resume];
if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.delegate webViewDidStartLoad:self];
}
这里如果传入的进度参数progress不为nil,那么就调用方法获得进度参数。并让任务af_URLSessionTask
开启,设置了已经开启的代理方法webViewDidStartLoad:
。
后记
本篇主要讲述AFN中UIWebView
的分类,详细的分析了指定request和指定MIME类型和编码的request下的请求。