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

我們應該看到過很多類似這樣的例子:某個控件擁有加載網(wǎng)絡圖片的能力纷妆。但這究竟是怎么做到的呢腌闯?看完這篇文章就明白了煌寇。

前言

這篇我們會介紹 AFNetworking 中的3個UIKit中的分類楚堤。UIActivityIndicatorView UIRefreshControl UIImageView。讀完本篇就能夠明白控件是如何顯示網(wǎng)絡圖片的塔粒。那么如果你有興趣结借,可以嘗試讓一個控件的layer也能夠加載網(wǎng)絡圖片。

提供的功能

我們解讀源碼不僅僅是了解內(nèi)部實現(xiàn)原理卒茬,還要讓開發(fā)者明白在這些分類中我能夠使用那些功能船老,因此在這個 提供的功能 小結中,我會把這3個分類提供的功能羅列出來圃酵,即使不看下邊的源碼解讀柳畔,也會有所收獲。

  1. UIActivityIndicatorView+AFNetworking UIActivityIndicatorView的這個分類最簡單辜昵,它只提供了一個方法:setAnimatingWithStateOfTask: 只要給UIActivityIndicatorView一個 task UIActivityIndicatorView會根據(jù)數(shù)據(jù)的加載情況 自動 開始動畫或者結束動畫荸镊。
  2. UIRefreshControl+AFNetworking UIRefreshControl的這個分類的使用跟上邊的UIActivityIndicatorView+AFNetworking一模一樣咽斧。
  3. UIImageView+AFNetworking UIImageView是最常用的顯示圖片的控件堪置。額外增加了 placeholderImage(替代圖片) 這個屬性和 success failure 這兩個block來自定義一些事件。最后增加了兩個取消某個狀態(tài)下的圖片下載的方法张惹。我們看下邊的圖片就好了:

UIActivityIndicatorView+AFNetworking

This category adds methods to the UIKit framework's UIActivityIndicatorView class. The methods in this category provide support for automatically starting and stopping animation depending on the loading state of a session task.

這個分類增加了UIActivityIndicatorView的一個方法舀锨。這個方法能夠提供根據(jù)task自動開始和結束動畫的功能

這個分類需要依賴 AFNetworking。需要監(jiān)聽AFNetworking中的網(wǎng)絡狀態(tài)的通知宛逗。按照通常的想法是坎匿,只要我監(jiān)聽了通知然后設置自己的狀態(tài)就完事了。然而這并不是好的設計。一個控件的某項新的功能應該交給一個專門負責這個功能的人去完成替蔬,這才是好的設計告私。

因此我們給UIActivityIndicatorView擴展了一個屬性af_notificationObserver,這個屬性是專門處理上邊說的事件的管理者。

好吧承桥,我們寫出偽代碼:

- (通知監(jiān)聽者 *)af_notificationObserver {
    return 通知監(jiān)聽者;
}
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
    監(jiān)聽者根據(jù)task來做一些事;
}

這樣寫的好處是:當我們想擴展別的功能的時候驻粟,只需要在添加一個其他功能的負責人就可以,所有的邏輯都是負責人自己實現(xiàn)凶异。這種思想簡直完美蜀撑。我們看 AFNetworking 中對上邊偽代碼的實現(xiàn)。相信大多數(shù)朋友應該知道剩彬,往分類中添加屬性使用Runtime酷麦,不明白的可以看這篇 Objective-C runtime的常見應用.

- (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver {
    AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
    if (notificationObserver == nil) {
        notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
        objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return notificationObserver;
}

- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
    [[self af_notificationObserver] setAnimatingWithStateOfTask:task];
}

我們來看看這個af_notificationObserver有什么話要說呢?

  • UIActivityIndicatorView *activityIndicatorView 既然讓我來管理UIActivityIndicatorView喉恋,那就必須拿到這個控件才行沃饶。
  • initWithActivityIndicatorView: 我不可能憑空出現(xiàn),通過這個方法創(chuàng)建我瀑晒。
  • setAnimatingWithStateOfTask: 我就是通過這個方法來操控UIActivityIndicatorView的绍坝。

這么看來,這個af_notificationObserver只需要上邊3個東東就足夠了苔悦,那么我們就剩下setAnimatingWithStateOfTask:這個方法的實現(xiàn)了轩褐。

- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
    
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

    // 移除 AFNetworking 的通知
    [notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
    [notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
    [notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
    
    // task != nil
    if (task) {
        
        // task的狀態(tài)不等于完成
        if (task.state != NSURLSessionTaskStateCompleted) {
            
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
            // 狀態(tài)為運行中就開始,否則為停止
            if (task.state == NSURLSessionTaskStateRunning) {
                [self.activityIndicatorView startAnimating];
            } else {
                [self.activityIndicatorView stopAnimating];
            }
#pragma clang diagnostic pop

            // 移除 AFNetworking 的通知
            [notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingTaskDidResumeNotification object:task];
            [notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidCompleteNotification object:task];
            [notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidSuspendNotification object:task];
        }
    }
}

#pragma mark -

- (void)af_startAnimating {
    dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
        [self.activityIndicatorView startAnimating];
#pragma clang diagnostic pop
    });
}

- (void)af_stopAnimating {
    dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
        [self.activityIndicatorView stopAnimating];
#pragma clang diagnostic pop
    });
}

UIImageView+AFNetworking

我們在 AFImageDownloader 那篇文章中提到過玖详,要異步顯示網(wǎng)絡上的圖片把介,就要把圖片數(shù)據(jù)緩存下來才行。因此蟋座,要賦予UIImageView這項功能拗踢,就需要使用 AFImageDownloader 來獲取圖片數(shù)據(jù)。

不知道大家發(fā)現(xiàn)沒有向臀,像這張圖片中的這些方法巢墅,

,我們只需要實現(xiàn)參數(shù)最多的那個方法就行了券膀。這應該就是所謂的 尾調(diào)函數(shù) 吧君纫。

首先我們先看看UIImageView擴展的一個屬性af_activeImageDownloadReceipt,這個屬性是圖片依據(jù)

@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@end

@implementation UIImageView (_AFNetworking)

- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
    return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}

- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

通過運行時為@selector(af_activeImageDownloadReceipt) 設置了關聯(lián)值芹彬,同樣的原理蓄髓。 sharedImageDownloader 也是這么設置的

+ (AFImageDownloader *)sharedImageDownloader {

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
#pragma clang diagnostic pop
}

+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
    objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

在這里說下這個objc_setAssociatedObject方法,其中第二個參數(shù)是一個地址舒帮,因此我們可以用@selector
或者自定義一個全局的const字段会喝,取它的地址陡叠。 看下邊的例子,我為UIImageView擴展了一個屬性abc肢执。

static const NSString *abcde;

@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@property (readwrite, nonatomic, strong)NSString *abc;
@end

@implementation UIImageView (_AFNetworking)

- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
    return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}

- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setAbc:(NSString *)abc {
    objc_setAssociatedObject(self, &abcde, abc, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)abc {
    return objc_getAssociatedObject(self, &abcde);
}

我在使用的時候

UIImageView *imageView = [[UIImageView alloc] init];
[imageView setValue:@"qwer" forKey:@"abc"];

NSString *str = [imageView valueForKey:@"abc"];
NSLog(@"%@",str);

--

- (void)cancelImageDownloadTask {
    if (self.af_activeImageDownloadReceipt != nil) {
        [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
        [self clearActiveDownloadInformation];
     }
}

- (void)clearActiveDownloadInformation {
    self.af_activeImageDownloadReceipt = nil;
}

- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest {
    return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}

來看這個核心方法枉阵,處理手法和之前的代碼如出一轍,值得學習的是预茄,核心方法中的判斷比較詳細岭妖。

- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
              placeholderImage:(UIImage *)placeholderImage
                       success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                       failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{

    // urlRequest 不正確
    if ([urlRequest URL] == nil) {
        // 取消下載任務
        [self cancelImageDownloadTask];
        // 賦值替代圖片
        self.image = placeholderImage;
        return;
    }

    // 如果當前活動的下載和本下載一樣,就返回
    if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
        return;
    }

    // 取消之前的下載任務
    [self cancelImageDownloadTask];

    // 取出downloader
    AFImageDownloader *downloader = [[self class] sharedImageDownloader];
    // 取出緩存
    id <AFImageRequestCache> imageCache = downloader.imageCache;

    //Use the image from the image cache if it exists
    UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
    if (cachedImage) {
        
        // 如果寫了success Block 就調(diào)動block反璃,但不會給image賦值
        if (success) {
            success(urlRequest, nil, cachedImage);
        } else {
            self.image = cachedImage;
        }
        [self clearActiveDownloadInformation];
    } else {
        
        // 沒有緩存的話昵慌,先設置替代圖片
        if (placeholderImage) {
            self.image = placeholderImage;
        }

        __weak __typeof(self)weakSelf = self;
        NSUUID *downloadID = [NSUUID UUID];
        AFImageDownloadReceipt *receipt;
        receipt = [downloader
                   downloadImageForURLRequest:urlRequest
                   withReceiptID:downloadID
                   success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                       if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                           if (success) {
                               success(request, response, responseObject);
                           } else if(responseObject) {
                               strongSelf.image = responseObject;
                           }
                           [strongSelf clearActiveDownloadInformation];
                       }

                   }
                   failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                        if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                            if (failure) {
                                failure(request, response, error);
                            }
                            [strongSelf clearActiveDownloadInformation];
                        }
                   }];

        self.af_activeImageDownloadReceipt = receipt;
    }
}

方法不是最重要的,重要是梳理出這一整套的邏輯和想法淮蜈,下面我們就來分析分析斋攀。

  1. 首先我們規(guī)定,使用這個分類每加載一次圖片生成一個af_activeImageDownloadReceipt憑據(jù)梧田,這個憑據(jù)一旦下載完成后淳蔼,需要置為nil。
  2. 我們使用上邊的這個最長的方法來加載圖片裁眯。
  3. 我們先判斷這個urlRequest是不是有效的鹉梨。有效就繼續(xù)往下走,無效的話取消之前的下載穿稳,然后賦值替代圖片存皂。說明如果urlRequest失效,同時也取消了之前的下載
  4. 好逢艘,到這里旦袋,說明urlRequest是正確的,那么我們再判斷是不是現(xiàn)在下載的跟之前正在下載的URL是一樣的它改?存在這樣一種操作疤孕,我寫了兩個上邊的方法
  5. 這一步要取消之前的下載任務
  6. 在緩存中取圖片,如果圖片存在央拖,那么再看看是否設置了success祭阀,設置了就調(diào)用這個block,否則就使用替代圖片鲜戒。
  7. 請求失敗處理方法同上邊6.一樣专控。

總結

通過對上邊的方法的解讀,我們就很容易的給別的控件添加異步加載功能了袍啡。使用上邊的方法且改動很少的代碼就能完成踩官。

推薦閱讀

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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末却桶,一起剝皮案震驚了整個濱河市境输,隨后出現(xiàn)的幾起案子蔗牡,更是在濱河造成了極大的恐慌,老刑警劉巖嗅剖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辩越,死亡現(xiàn)場離奇詭異,居然都是意外死亡信粮,警方通過查閱死者的電腦和手機黔攒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來强缘,“玉大人督惰,你說我怎么就攤上這事÷玫啵” “怎么了赏胚?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長商虐。 經(jīng)常有香客問我觉阅,道長,這世上最難降的妖魔是什么秘车? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任典勇,我火速辦了婚禮,結果婚禮上叮趴,老公的妹妹穿的比我還像新娘割笙。我一直安慰自己,他們只是感情好眯亦,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布咳蔚。 她就那樣靜靜地躺著,像睡著了一般搔驼。 火紅的嫁衣襯著肌膚如雪谈火。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天舌涨,我揣著相機與錄音糯耍,去河邊找鬼。 笑死囊嘉,一個胖子當著我的面吹牛温技,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扭粱,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼舵鳞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琢蛤?” 一聲冷哼從身側(cè)響起蜓堕,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抛虏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后套才,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迂猴,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年背伴,在試婚紗的時候發(fā)現(xiàn)自己被綠了沸毁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡傻寂,死狀恐怖息尺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疾掰,我是刑警寧澤掷倔,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站个绍,受9級特大地震影響勒葱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜巴柿,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一凛虽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧广恢,春花似錦凯旋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至糠聪,卻和暖如春荒椭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舰蟆。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工趣惠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人身害。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓味悄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親塌鸯。 傳聞我的和親對象是個殘疾皇子侍瑟,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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