AFNetworking源碼探究(二十三) —— UIKit相關(guān)之UIWebView+AFNetworking分類(六)

版本記錄

版本號 時間
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加載引出的鏈接,并且可以確保canGoBackcanGoForward正確地更新它們的值透绩。


獲取數(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下的請求花嘶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蹦漠,隨后出現(xiàn)的幾起案子椭员,更是在濱河造成了極大的恐慌,老刑警劉巖笛园,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隘击,死亡現(xiàn)場離奇詭異,居然都是意外死亡研铆,警方通過查閱死者的電腦和手機埋同,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚜印,“玉大人莺禁,你說我怎么就攤上這事≌常” “怎么了哟冬?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長忆绰。 經(jīng)常有香客問我浩峡,道長,這世上最難降的妖魔是什么错敢? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任翰灾,我火速辦了婚禮,結(jié)果婚禮上稚茅,老公的妹妹穿的比我還像新娘纸淮。我一直安慰自己,他們只是感情好亚享,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布咽块。 她就那樣靜靜地躺著,像睡著了一般欺税。 火紅的嫁衣襯著肌膚如雪侈沪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天晚凿,我揣著相機與錄音亭罪,去河邊找鬼。 笑死歼秽,一個胖子當著我的面吹牛应役,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼箩祥,長吁一口氣:“原來是場噩夢啊……” “哼呻惕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起滥比,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎做院,沒想到半個月后盲泛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡键耕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年寺滚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屈雄。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡村视,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出酒奶,到底是詐尸還是另有隱情蚁孔,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布惋嚎,位于F島的核電站杠氢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏另伍。R本人自食惡果不足惜鼻百,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摆尝。 院中可真熱鬧温艇,春花似錦、人聲如沸堕汞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽臼朗。三九已至邻寿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間视哑,已是汗流浹背绣否。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挡毅,地道東北人蒜撮。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親段磨。 傳聞我的和親對象是個殘疾皇子取逾,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容