AFNetworking 3.0 源碼解讀(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

AFNetworking的源碼解讀馬上就結(jié)束了,這一篇應(yīng)該算是倒數(shù)第二篇辐啄,下一篇會(huì)是對(duì)AFNetworking中的技術(shù)點(diǎn)進(jìn)行總結(jié)采章。

前言

上一篇我們總結(jié)了 UIActivityIndicatorView UIRefreshControl UIImageView 這3個(gè)控件的分類。那么這一篇就總結(jié)下剩余的3個(gè)分類:UIButton UIProgressView UIWebView 壶辜。

UIButton+AFNetworking

UIButton跟圖片相關(guān)的屬性大概有兩個(gè)悯舟,ImageBackgroundImage.所以這個(gè)分類就是賦予他們異步加載圖片的能力。

其中核心方法為:


示例代碼:

static char AFImageDownloadReceiptNormal;
static char AFImageDownloadReceiptHighlighted;
static char AFImageDownloadReceiptSelected;
static char AFImageDownloadReceiptDisabled;

static const char * af_imageDownloadReceiptKeyForState(UIControlState state) {
    switch (state) {
        case UIControlStateHighlighted:
            return &AFImageDownloadReceiptHighlighted;
        case UIControlStateSelected:
            return &AFImageDownloadReceiptSelected;
        case UIControlStateDisabled:
            return &AFImageDownloadReceiptDisabled;
        case UIControlStateNormal:
        default:
            return &AFImageDownloadReceiptNormal;
    }
}

- (AFImageDownloadReceipt *)af_imageDownloadReceiptForState:(UIControlState)state {
    return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_imageDownloadReceiptKeyForState(state));
}

- (void)af_setImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
                           forState:(UIControlState)state
{
    objc_setAssociatedObject(self, af_imageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

我們分析下上邊的代碼砸民。我們都知道UIButton有4種狀態(tài)图谷。這個(gè)分類能夠支持不同的狀態(tài)加載不同的圖片。同樣我們也知道阱洪,每一個(gè)圖片的加載便贵,都需要一個(gè)AFImageDownloadReceipt憑證。所以冗荸,我們要為UIButton擴(kuò)展一個(gè)根據(jù)狀態(tài)獲取憑證的方法承璃,就是:af_imageDownloadReceiptForState:。既然有獲取憑證的方法蚌本,就應(yīng)該有根據(jù)狀態(tài)設(shè)置憑證的方法盔粹,那就是:af_setImageDownloadReceipt: forState:.

上邊的af_imageDownloadReceiptKeyForState方法的作用就是為運(yùn)行時(shí)提供一個(gè)key,這個(gè)key是一個(gè)內(nèi)存地址程癌。也可使用@Selector()舷嗡。同理,下邊的代碼擴(kuò)展了BackgroundImage嵌莉,原理同上进萄,就不做解釋了

示例代碼:

static char AFBackgroundImageDownloadReceiptNormal;
static char AFBackgroundImageDownloadReceiptHighlighted;
static char AFBackgroundImageDownloadReceiptSelected;
static char AFBackgroundImageDownloadReceiptDisabled;

static const char * af_backgroundImageDownloadReceiptKeyForState(UIControlState state) {
    switch (state) {
        case UIControlStateHighlighted:
            return &AFBackgroundImageDownloadReceiptHighlighted;
        case UIControlStateSelected:
            return &AFBackgroundImageDownloadReceiptSelected;
        case UIControlStateDisabled:
            return &AFBackgroundImageDownloadReceiptDisabled;
        case UIControlStateNormal:
        default:
            return &AFBackgroundImageDownloadReceiptNormal;
    }
}

- (AFImageDownloadReceipt *)af_backgroundImageDownloadReceiptForState:(UIControlState)state {
    return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state));
}

- (void)af_setBackgroundImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
                                     forState:(UIControlState)state
{
    objc_setAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

示例代碼:

// 使用運(yùn)行時(shí)設(shè)置sharedImageDownloader
+ (AFImageDownloader *)sharedImageDownloader {

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
#pragma clang diagnostic pop
}
// 使用運(yùn)行時(shí)設(shè)置setSharedImageDownloader:
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
    objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

如果對(duì)這個(gè)分類的核心方法感興趣的話,可以參考上一篇锐峭。里邊有詳細(xì)的解釋中鼠,原理和代碼非常非常像,在這里為了節(jié)省篇幅就不做多余的說(shuō)明了沿癞。在這個(gè)分類中下邊圖片的那行代碼可以注釋掉援雇。

UIProgressView+AFNetworking

UIProgressView的這個(gè)分類,實(shí)現(xiàn)原理就是監(jiān)聽(tīng)NSURLSessionUploadTask或者NSURLSessionDownloadTask中的"state" "countOfBytesSent" "countOfBytesReceived" 椎扬。然后設(shè)置進(jìn)度就可以了惫搏。

示例代碼:

- (BOOL)af_uploadProgressAnimated {
    return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_uploadProgressAnimated)) boolValue];
}

- (void)af_setUploadProgressAnimated:(BOOL)animated {
    objc_setAssociatedObject(self, @selector(af_uploadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)af_downloadProgressAnimated {
    return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue];
}

- (void)af_setDownloadProgressAnimated:(BOOL)animated {
    objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

<font color=orange>看到上邊的這四個(gè)方法,我突然間明白蚕涤,假如我們需要一個(gè)屬性記錄某一個(gè)狀態(tài)的話筐赔,通常我們會(huì)寫一個(gè)屬性,但是看上邊的代碼钻趋,是通過(guò)擴(kuò)展了幾個(gè)方法來(lái)達(dá)到記錄狀態(tài)的目的川陆。這就說(shuō)明同樣一個(gè)結(jié)果,可以有不同的實(shí)現(xiàn)手段。但我不太明白這兩個(gè)的區(qū)別是什么较沪?</font>

示例代碼:

- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
                                   animated:(BOOL)animated
{
    [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
    [task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];

    [self af_setUploadProgressAnimated:animated];
}

- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
                                     animated:(BOOL)animated
{
    [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
    [task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];

    [self af_setDownloadProgressAnimated:animated];
}

示例代碼:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(__unused NSDictionary *)change
                       context:(void *)context
{
    // 判斷是不是我們需要的監(jiān)聽(tīng)對(duì)象
    if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
        
        // 上傳
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            if ([object countOfBytesExpectedToSend] > 0) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
                });
            }
        }

        // 下載
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            if ([object countOfBytesExpectedToReceive] > 0) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated];
                });
            }
        }

        // 狀態(tài)
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
            if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
                @try {
                    
                    // 移除state
                    [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];

                    // 移除countOfBytesSent
                    if (context == AFTaskCountOfBytesSentContext) {
                        [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
                    }

                    // 移除countOfBytesReceived
                    if (context == AFTaskCountOfBytesReceivedContext) {
                        [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
                    }
                }
                @catch (NSException * __unused exception) {}
            }
        }
    }
}

UIWebView+AFNetworking

UIWebView的這個(gè)分類是這幾個(gè)分類中最讓我驚訝的一個(gè)鳞绕。讓我真正認(rèn)識(shí)到條條大路通羅馬到底是什么意思。有時(shí)候人的思想確實(shí)會(huì)被固有的思維所束縛尸曼。**這里只是用了UIWebView的loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString )textEncodingName baseURL:(NSURL )baseURL方法

你會(huì)發(fā)現(xiàn)使用這個(gè)分類配合UIWebView们何,所有的事情都變得很簡(jiǎn)單。

示例代碼:

@interface UIWebView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setURLSessionTask:) NSURLSessionDataTask *af_URLSessionTask;
@end

@implementation UIWebView (_AFNetworking)

- (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);
}

@end

為UIWebView擴(kuò)展了一個(gè)私有屬性af_URLSessionTask控轿,定義為每一次請(qǐng)求冤竹,就會(huì)對(duì)應(yīng)一個(gè)af_URLSessionTask。

示例代碼:

- (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];
    });

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    return objc_getAssociatedObject(self, @selector(sessionManager)) ?: _af_defaultHTTPSessionManager;
#pragma clang diagnostic pop
}

- (void)setSessionManager:(AFHTTPSessionManager *)sessionManager {
    objc_setAssociatedObject(self, @selector(sessionManager), sessionManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

為UIWebView擴(kuò)展的一個(gè)sessionManager屬性茬射。實(shí)現(xiàn)了setter和getter方法鹦蠕。這樣在后邊直接使用self.sessionManager就可以,不用創(chuàng)建了在抛。

示例代碼:

- (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
{
    // 檢查參數(shù)
    NSParameterAssert(request);

    // 如果正處于運(yùn)行或者暫停裝狀態(tài)钟病,就取消之前的任務(wù)task并設(shè)置為nil
    if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
        [self.af_URLSessionTask cancel];
    }
    self.af_URLSessionTask = nil;

    __weak __typeof(self)weakSelf = self;
    NSURLSessionDataTask *dataTask;
    dataTask = [self.sessionManager
            GET:request.URL.absoluteString
            parameters:nil
            progress:nil
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                __strong __typeof(weakSelf) strongSelf = weakSelf;
                
                // 請(qǐng)求成功后,調(diào)用success block
                if (success) {
                    success((NSHTTPURLResponse *)task.response, responseObject);
                }
                // 顯示數(shù)據(jù)
                [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[task.currentRequest URL]];

                // 調(diào)用webViewDidFinishLoad
                if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
                    [strongSelf.delegate webViewDidFinishLoad:strongSelf];
                }
            }
            failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
                if (failure) {
                    failure(error);
                }
            }];
    self.af_URLSessionTask = dataTask;
    
    // 設(shè)置progress刚梭,這個(gè)來(lái)自于self.sessionManager
    if (progress != nil) {
        *progress = [self.sessionManager downloadProgressForTask:dataTask];
    }
    
    // 開(kāi)啟任務(wù)
    [self.af_URLSessionTask resume];

    // 調(diào)用webViewDidStartLoad方法
    if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
        [self.delegate webViewDidStartLoad:self];
    }
}

--

- (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];
}

總結(jié)

就一句話肠阱,UIWebView+AFNetworking模擬了UIWebView加載數(shù)據(jù)的過(guò)程。模擬朴读,模擬屹徘,模擬。衅金。噪伊。

推薦閱讀

AFNetworking 3.0 源碼解讀(一)之 AFNetworkReachabilityManager

AFNetworking 3.0 源碼解讀(二)之 AFSecurityPolicy

AFNetworking 3.0 源碼解讀(三)之 AFURLRequestSerialization

AFNetworking 3.0 源碼解讀(四)之 AFURLResponseSerialization

AFNetworking 3.0 源碼解讀(五)之 AFURLSessionManager

AFNetworking 3.0 源碼解讀(六)之 AFHTTPSessionManager

AFNetworking 3.0 源碼解讀(七)之 AFAutoPurgingImageCache

AFNetworking 3.0 源碼解讀(八)之 AFImageDownloader

AFNetworking 3.0 源碼解讀(九)之 AFNetworkActivityIndicatorManager

AFNetworking 3.0 源碼解讀(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市典挑,隨后出現(xiàn)的幾起案子酥宴,更是在濱河造成了極大的恐慌啦吧,老刑警劉巖您觉,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異授滓,居然都是意外死亡琳水,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門般堆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)在孝,“玉大人,你說(shuō)我怎么就攤上這事淮摔∷骄冢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵和橙,是天一觀的道長(zhǎng)仔燕。 經(jīng)常有香客問(wèn)我造垛,道長(zhǎng),這世上最難降的妖魔是什么晰搀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任五辽,我火速辦了婚禮,結(jié)果婚禮上外恕,老公的妹妹穿的比我還像新娘杆逗。我一直安慰自己,他們只是感情好鳞疲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布罪郊。 她就那樣靜靜地躺著,像睡著了一般尚洽。 火紅的嫁衣襯著肌膚如雪排龄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天翎朱,我揣著相機(jī)與錄音橄维,去河邊找鬼。 笑死拴曲,一個(gè)胖子當(dāng)著我的面吹牛争舞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澈灼,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼竞川,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了叁熔?” 一聲冷哼從身側(cè)響起委乌,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荣回,沒(méi)想到半個(gè)月后遭贸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡心软,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年壕吹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片删铃。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耳贬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猎唁,到底是詐尸還是另有隱情咒劲,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站腐魂,受9級(jí)特大地震影響慕的,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挤渔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一肮街、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧判导,春花似錦嫉父、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至擂红,卻和暖如春仪际,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昵骤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工树碱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人变秦。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓成榜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蹦玫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赎婚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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