版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.03.06 |
前言
我們做APP發(fā)起網(wǎng)絡請求,都離不開一個非常有用的框架AFNetworking惫谤,可以說這個框架的知名度已經(jīng)超過了蘋果的底層網(wǎng)絡請求部分,很多人可能不知道蘋果底層是如何發(fā)起網(wǎng)絡請求的患雏,但是一定知道
AFNetworking
或渤,接下來幾篇我們就一起詳細的解析一下這個框架。感興趣的可以看上面寫的幾篇旷余。
1. AFNetworking源碼探究(一) —— 基本介紹
2. AFNetworking源碼探究(二) —— GET請求實現(xiàn)之NSURLSessionDataTask實例化(一)
3. AFNetworking源碼探究(三) —— GET請求實現(xiàn)之任務進度設(shè)置和通知監(jiān)聽(一)
4. AFNetworking源碼探究(四) —— GET請求實現(xiàn)之代理轉(zhuǎn)發(fā)思想(一)
5. AFNetworking源碼探究(五) —— AFURLSessionManager中NSURLSessionDelegate詳細解析(一)
6. AFNetworking源碼探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate詳細解析(一)
7. AFNetworking源碼探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate詳細解析(一)
8. AFNetworking源碼探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate詳細解析(一)
9. AFNetworking源碼探究(九) —— AFURLSessionManagerTaskDelegate中三個轉(zhuǎn)發(fā)代理方法詳細解析(一)
10. AFNetworking源碼探究(十) —— 數(shù)據(jù)解析之數(shù)據(jù)解析架構(gòu)的分析(一)
11. AFNetworking源碼探究(十一) —— 數(shù)據(jù)解析之子類中協(xié)議方法的實現(xiàn)(二)
12. AFNetworking源碼探究(十二) —— 數(shù)據(jù)解析之子類中協(xié)議方法的實現(xiàn)(三)
13. AFNetworking源碼探究(十三) —— AFSecurityPolicy與安全認證 (一)
14. AFNetworking源碼探究(十四) —— AFSecurityPolicy與安全認證 (二)
15. AFNetworking源碼探究(十五) —— 請求序列化之架構(gòu)分析(一)
16. AFNetworking源碼探究(十六) —— 請求序列化之協(xié)議方法的實現(xiàn)(二)
17. AFNetworking源碼探究(十七) —— _AFURLSessionTaskSwizzling實現(xiàn)方法交換(轉(zhuǎn)載)(一)
18. AFNetworking源碼探究(十八) —— UIKit相關(guān)之AFNetworkActivityIndicatorManager(一)
19. AFNetworking源碼探究(十九) —— UIKit相關(guān)之幾個分類(二)
20. AFNetworking源碼探究(二十) —— UIKit相關(guān)之AFImageDownloader圖像下載(三)
21. AFNetworking源碼探究(二十一) —— UIKit相關(guān)之UIImageView+AFNetworking分類(四)
22. AFNetworking源碼探究(二十二) —— UIKit相關(guān)之UIButton+AFNetworking分類(五)
回顧
上一篇講述了UIButton+AFNetworking的UIButton的一個分類绢记。分析了其下載器的下載、圖像的下載以及背景圖像的下載正卧。這一篇就繼續(xù)講述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類型和文本編碼格式的指定請求的數(shù)據(jù)
@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
類添加方法签孔。 此類別中的方法可以更好地控制請求周期,包括進度監(jiān)視和成功/失敗處理窘行。
在使用這些類別方法時骏啰,請確保為webView分配delegate
,它適當?shù)貙崿F(xiàn)- webView:shouldStartLoadWithRequest:navigationType:
抽高。 這允許通過AFNetworking加載引出的鏈接,并且可以確保canGoBack
和canGoForward
正確地更新它們的值透绩。
獲取數(shù)據(jù)任務
這里面實現(xiàn)了UIWebView
的另外一個分類_AFNetworking
翘骂,利用runtime獲取了數(shù)據(jù)任務壁熄。
- (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
中實現(xiàn)的,實現(xiàn)方式還是使用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對象的實現(xiàn)
- (AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
static AFHTTPResponseSerializer <AFURLResponseSerialization> *_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<AFURLResponseSerialization> *)responseSerializer {
objc_setAssociatedObject(self, @selector(responseSerializer), responseSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
請求數(shù)據(jù)的實現(xiàn)
主要就是下面兩個方法草丧。
- (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;
其實看一下源碼就知道,上面方法是通過調(diào)用下面的方法實現(xiàn)的莹桅,傳遞的參數(shù)MIMEType和textEncodingName都為nil昌执,并在數(shù)據(jù)回來中進行了處理。下面我們就一起看一下诈泼。
1. 加載指定請求
主要看一下實現(xiàn)
- (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];
}
我們看一下在成功回調(diào)做的處理懂拾。
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
存在岖赋,那么就進行相關(guān)編碼轉(zhuǎn)化,最后就是利用生成的編碼格式瓮孙,生成NSString類型的數(shù)據(jù)唐断,并作為成功回調(diào)的參數(shù)進行傳遞。
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];
}
}
這個實現(xiàn)主要做了下面幾個工作:
- 任務狀態(tài)的判斷及邏輯處理
- AFHTTPSessionManager對象開啟指定request的請求脸甘,并處理成功和失敗的回調(diào)
- 處理進度,重新開啟任務
(a) 任務狀態(tài)的判斷及邏輯處理
主要對應下面這段代碼
NSParameterAssert(request);
if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
[self.af_URLSessionTask cancel];
}
self.af_URLSessionTask = nil;
這里首選使用斷言NSParameterAssert
進行參數(shù)判斷偏灿,參數(shù)為空就崩潰丹诀。然后判斷任務的狀態(tài),如果任務正在進行或者暫停菩混,那么就取消該任務忿墅。并將任務指針設(shè)置為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;
這里邏輯也是很清晰了吧疚脐,如果存在錯誤,那么就回調(diào)failure(error)
邢疙,否則就說明沒有失敗棍弄,那么就進行回調(diào)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];
}
接著就是利用上面方法請求數(shù)據(jù)疟游,并設(shè)置了代理方法呼畸。
(c) 處理進度,重新開啟任務
if (progress != nil) {
*progress = [self.sessionManager downloadProgressForTask:dataTask];
}
[self.af_URLSessionTask resume];
if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.delegate webViewDidStartLoad:self];
}
這里如果傳入的進度參數(shù)progress不為nil颁虐,那么就調(diào)用方法獲得進度參數(shù)蛮原。并讓任務af_URLSessionTask
開啟,設(shè)置了已經(jīng)開啟的代理方法webViewDidStartLoad:
另绩。
后記
本篇主要講述AFN中UIWebView
的分類儒陨,詳細的分析了指定request和指定MIME類型和編碼的request下的請求花嘶。