AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking...
我們應(yīng)該看到過(guò)很多類似這樣的例子:某個(gè)控件擁有加載網(wǎng)絡(luò)圖片的能力。但這究竟是怎么做到的呢?看完這篇文章就明白了。
前言
這篇我們會(huì)介紹 AFNetworking 中的3個(gè)UIKit中的分類。UIActivityIndicatorView UIRefreshControl UIImageView。讀完本篇就能夠明白控件是如何顯示網(wǎng)絡(luò)圖片的。那么如果你有興趣,可以嘗試讓一個(gè)控件的layer也能夠加載網(wǎng)絡(luò)圖片。
提供的功能
我們解讀源碼不僅僅是了解內(nèi)部實(shí)現(xiàn)原理,還要讓開(kāi)發(fā)者明白在這些分類中我能夠使用那些功能,因此在這個(gè) 提供的功能 小結(jié)中,我會(huì)把這3個(gè)分類提供的功能羅列出來(lái),即使不看下邊的源碼解讀,也會(huì)有所收獲。
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.
這個(gè)分類增加了UIActivityIndicatorView的一個(gè)方法。這個(gè)方法能夠提供根據(jù)task自動(dòng)開(kāi)始和結(jié)束動(dòng)畫的功能
這個(gè)分類需要依賴 AFNetworking。需要監(jiān)聽(tīng)AFNetworking中的網(wǎng)絡(luò)狀態(tài)的通知。按照通常的想法是,只要我監(jiān)聽(tīng)了通知然后設(shè)置自己的狀態(tài)就完事了。然而這并不是好的設(shè)計(jì)。一個(gè)控件的某項(xiàng)新的功能應(yīng)該交給一個(gè)專門負(fù)責(zé)這個(gè)功能的人去完成,這才是好的設(shè)計(jì)。
因此我們給UIActivityIndicatorView擴(kuò)展了一個(gè)屬性af_notificationObserver,這個(gè)屬性是專門處理上邊說(shuō)的事件的管理者。
好吧,我們寫出偽代碼:
- (通知監(jiān)聽(tīng)者 *)af_notificationObserver {return 通知監(jiān)聽(tīng)者; } - (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {監(jiān)聽(tīng)者根據(jù)task來(lái)做一些事; }這樣寫的好處是:當(dāng)我們想擴(kuò)展別的功能的時(shí)候,只需要在添加一個(gè)其他功能的負(fù)責(zé)人就可以,所有的邏輯都是負(fù)責(zé)人自己實(shí)現(xiàn)。這種思想簡(jiǎn)直完美。我們看 AFNetworking 中對(duì)上邊偽代碼的實(shí)現(xiàn)。相信大多數(shù)朋友應(yīng)該知道,往分類中添加屬性使用Runtime,不明白的可以看這篇 Objective-C runtime的常見(jiàn)應(yīng)用.
- (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]; }我們來(lái)看看這個(gè)af_notificationObserver有什么話要說(shuō)呢?
- UIActivityIndicatorView *activityIndicatorView 既然讓我來(lái)管理UIActivityIndicatorView,那就必須拿到這個(gè)控件才行。
- initWithActivityIndicatorView: 我不可能憑空出現(xiàn),通過(guò)這個(gè)方法創(chuàng)建我。
- setAnimatingWithStateOfTask: 我就是通過(guò)這個(gè)方法來(lái)操控UIActivityIndicatorView的。
這么看來(lái),這個(gè)af_notificationObserver只需要上邊3個(gè)東東就足夠了,那么我們就剩下setAnimatingWithStateOfTask:這個(gè)方法的實(shí)現(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 != nilif (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)為運(yùn)行中就開(kā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
我們?cè)?AFImageDownloader 那篇文章中提到過(guò),要異步顯示網(wǎng)絡(luò)上的圖片,就要把圖片數(shù)據(jù)緩存下來(lái)才行。因此,要賦予UIImageView這項(xiàng)功能,就需要使用 AFImageDownloader 來(lái)獲取圖片數(shù)據(jù)。
不知道大家發(fā)現(xiàn)沒(méi)有,像這張圖片中的這些方法,,我們只需要實(shí)現(xiàn)參數(shù)最多的那個(gè)方法就行了。這應(yīng)該就是所謂的 尾調(diào)函數(shù) 吧。
首先我們先看看UIImageView擴(kuò)展的一個(gè)屬性af_activeImageDownloadReceipt,這個(gè)屬性是圖片依據(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); }通過(guò)運(yùn)行時(shí)為@selector(af_activeImageDownloadReceipt) 設(shè)置了關(guān)聯(lián)值,同樣的原理。 sharedImageDownloader 也是這么設(shè)置的
+ (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); }在這里說(shuō)下這個(gè)objc_setAssociatedObject方法,其中第二個(gè)參數(shù)是一個(gè)地址,因此我們可以用@selector
或者自定義一個(gè)全局的const字段,取它的地址。 看下邊的例子,我為UIImageView擴(kuò)展了一個(gè)屬性abc。
我在使用的時(shí)候
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]; }來(lái)看這個(gè)核心方法,處理手法和之前的代碼如出一轍,值得學(xué)習(xí)的是,核心方法中的判斷比較詳細(xì)。
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequestplaceholderImage:(UIImage *)placeholderImagesuccess:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))successfailure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {// urlRequest 不正確if ([urlRequest URL] == nil) {// 取消下載任務(wù)[self cancelImageDownloadTask];// 賦值替代圖片self.image = placeholderImage;return;}// 如果當(dāng)前活動(dòng)的下載和本下載一樣,就返回if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){return;}// 取消之前的下載任務(wù)[self cancelImageDownloadTask];// 取出downloaderAFImageDownloader *downloader = [[self class] sharedImageDownloader];// 取出緩存id <AFImageRequestCache> imageCache = downloader.imageCache;//Use the image from the image cache if it existsUIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];if (cachedImage) {// 如果寫了success Block 就調(diào)動(dòng)block,但不會(huì)給image賦值if (success) {success(urlRequest, nil, cachedImage);} else {self.image = cachedImage;}[self clearActiveDownloadInformation];} else {// 沒(méi)有緩存的話,先設(shè)置替代圖片if (placeholderImage) {self.image = placeholderImage;}__weak __typeof(self)weakSelf = self;NSUUID *downloadID = [NSUUID UUID];AFImageDownloadReceipt *receipt;receipt = [downloaderdownloadImageForURLRequest:urlRequestwithReceiptID:downloadIDsuccess:^(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;} }方法不是最重要的,重要是梳理出這一整套的邏輯和想法,下面我們就來(lái)分析分析。
總結(jié)
通過(guò)對(duì)上邊的方法的解讀,我們就很容易的給別的控件添加異步加載功能了。使用上邊的方法且改動(dòng)很少的代碼就能完成。
推薦閱讀
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
與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android 小技巧
- 下一篇: 8字箴言:尊重、尽责、开放、创新