UIImageView+AFNetworking源碼解析

前言

最近在使用SDWebImage時突然想到AFNetworking也有類似于SD中UIImageView+WebCache的功能,因此,查看了AF中相關(guān)代碼。

UIImageView+AFNetworking簡單使用

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    static  NSString* ID = @"cutomeCell";
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:ID];
    
    FYXGirlItem *item = self.items[indexPath.row];
    cell.textLabel.text = item.name;
    cell.detailTextLabel.text = item.download;
    
    [cell.imageView setImageWithURL:[NSURL URLWithString:item.icon] placeholderImage:[UIImage imageNamed:@"placeholder"]];
    return cell;
}

如上所示,在使用方面與SD差別不是很大。

UIImageView+AFNetworking介紹

1)作用

  • 用于解決異步加載圖片時的卡頓問題(例如,UITableViewCell在加載圖片時的延遲問題)
  1. 相關(guān)的頭文件和.m文件

① UIImageView+AFNetworking.h / UIImageView+AFNetworking.m(用戶接觸的類,用于取消、下載圖片等操作)
② AFImageDownloader.h / AFImageDownloader.m(真正實現(xiàn)取消、下載圖片等操作)
③ AFAutoPurgingImageCache.h / AFAutoPurgingImageCache.m(該框架內(nèi)部實現(xiàn)的一個內(nèi)容緩存類)

3)公共接口

  • 由于各個文件的接口有很多,這里只列出部分重要的接口,剩余的接口可以參考源代碼:AFNetworking

① UIImageView+AFNetworking文件全部公共接口

/**
 *  用于設(shè)置AFImageDownloader下載器(用到了關(guān)聯(lián)對象)
 */
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;

/**
 *  用于獲取AFImageDownloader下載器(用到了關(guān)聯(lián)對象)
 */
+ (AFImageDownloader *)sharedImageDownloader;   

// 傳遞URL來設(shè)置UIImageView的圖片
- (void)setImageWithURL:(NSURL *)url;

// 傳遞URL設(shè)置UIImageView的圖片,同時,當(dāng)圖片沒有下載完時UIImageView只顯示占位圖   
- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(nullable UIImage *)placeholderImage;

// 傳遞URL和占位圖,并且下載圖片成功或者失敗后有回調(diào)
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
              placeholderImage:(nullable UIImage *)placeholderImage
                       success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                       failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;  

// 取消下載的任務(wù) 
- (void)cancelImageDownloadTask;

② AFImageDownloader文件


// 獲取AFImageDownloader的單例對象    
+ (instancetype)defaultInstance;      

// 獲取默認(rèn)的NSURLCache  
+ (NSURLCache *)defaultURLCache;      

// 重新實現(xiàn)init方法  
- (instancetype)init;     

// 初始化AFHTTPSessionManager對象及其該分類相關(guān)的屬性    
- (instancetype)initWithSessionManager (AFHTTPSessionManager *)sessionManager
                downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
                maximumActiveDownloads:(NSInteger)maximumActiveDownloads
                            imageCache:(nullable id <AFImageRequestCache>)imageCache;     

// 通過請求下載圖片    
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                        success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                        failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;    

// 通過請求下載圖片,同時,指定了UUID   
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                 withReceiptID:(NSUUID *)receiptID
                                                        success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                        failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;    

// 取消下載操作        
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt;

③ AFAutoPurgingImageCache文件

// 該類實現(xiàn)內(nèi)存緩存,是通過NSDictionary來實現(xiàn)的

/**
  聲明了一系列將圖片放入緩存、從緩存移除圖片以及獲取緩存中圖片的API 
 */
@protocol AFImageCache <NSObject>

/**
    將圖片放入緩存identifier是字典的key值,用于存儲key對應(yīng)的UIImage
 */
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;

/**
  通過identifier(也就是Key值)將對應(yīng)的的UIImage從緩存(字典)移除  
 */
- (BOOL)removeImageWithIdentifier:(NSString *)identifier;

/**
  很黃很暴力,一次性將緩存清空  
 */
- (BOOL)removeAllImages;

/**
  通過identifier獲取圖片
 */
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;
@end

--- --
/**
   該協(xié)議是對AFImageCache的擴(kuò)充,主要是將請求的URL和后面identifier替代之前AFImageCache之前的identifier。方法的內(nèi)部實現(xiàn)還是使用了AFImageCache的方法
 */
@protocol AFImageRequestCache <AFImageCache>

/**
  將圖片放入緩存
 */
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;

/**
  將圖片從緩存移除   
 */
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;

/**
  獲取圖片
 */
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;

@end

/**
   實現(xiàn)內(nèi)存緩存的具體類
 */
@interface AFAutoPurgingImageCache : NSObject <AFImageRequestCache>

/**    
  初始化該緩存類,默認(rèn)情況下memoryCapicty大小是100M,而圖片的緩存大小大于memoryCapicty時,會清除圖片直到60M停止   
 */
- (instancetype)init;

/**
   一次性設(shè)置緩存的內(nèi)存大小
 ①  memoryCapacity :緩存區(qū)大小
 ② preferredMemoryCapaciry:圖片緩存超出緩存區(qū)大小后清除圖片后合適的剩余緩存大小
 */
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;

@end

具體實現(xiàn)流程

  1. 當(dāng)我們想個ImageView的圖片進(jìn)行賦值時,我們會調(diào)用以下代碼中的一種:

// 調(diào)用了②,占位圖片為空
① - (void)setImageWithURL:(NSURL *)url {
    [self setImageWithURL:url placeholderImage:nil];
}

//  調(diào)用了③ success和failure回調(diào)為nil         
② - (void)setImageWithURL:(NSURL *)url
       placeholderImage:(UIImage *)placeholderImage
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];

    [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}

// 最終實現(xiàn)地方
// 該方法思路:  
/**  
*  1) 判斷傳入的請求中URL是否為空;若為空,則取消下載,imageView顯示占位圖片,否則,進(jìn)入下一步判斷          
*  2) 判斷當(dāng)前的請求是否有task處于活動狀態(tài)(意思可能用戶單位時間內(nèi)多次發(fā)了同一個請求,而在這個請求發(fā)送時已經(jīng)有下載的Task執(zhí)行了,這個請求將結(jié)束)                
*  3) 根據(jù)請求查看內(nèi)存緩存中是否目前有當(dāng)前請求的圖片,若有則查看success有無回調(diào),有回調(diào)則將圖片回調(diào)回去,否則,直接設(shè)置本ImageView的image    
*  4) 若本地內(nèi)存緩存沒有數(shù)據(jù),則先將imageView的image設(shè)置為占位圖片并生成UUID,然后將請求和UUID發(fā)送出去; 若成功,則檢查UUID是否和之前生成的UUID相等,以防止 
*  數(shù)據(jù)出錯,沒有問題則根據(jù)success是否有回調(diào)將圖片發(fā)送出去;若失敗,也檢查UUID是否相等,并根據(jù)有無failure回調(diào),將錯誤信息輸出       
*
*/
③  - (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
{
    if ([urlRequest URL] == nil) {
        [self cancelImageDownloadTask];
        self.image = placeholderImage;
        return;
    }

    if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
        return;
    }

    [self cancelImageDownloadTask];
    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) {
        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. 在1)中說了,內(nèi)存緩存沒有使用請求和UUID去下載圖片,那么現(xiàn)在研究其下載操作

這段代碼有點長,耐心看一下

去下載圖片時的思路:

  1. 檢查請求的URL是否為空,失敗則則返回信息(感覺這一句有點多余,因為調(diào)用這個方法之間已經(jīng)檢查了URL是否為空)
  2. 在Task沒有執(zhí)行時,多個請求可能會同時達(dá)到該方法,因此,需要一個字典存儲該URL對應(yīng)的AFImageDownloaderMergedTask,若有,則取出并創(chuàng)建一個AFImageDownloaderResponseHandler
    放入該自定義Task的一個響應(yīng)數(shù)組中。并將該自定義task的NSURLSessionDataTask賦值給task;若沒有,則進(jìn)行下一步
  3. 由于對于磁盤緩存作者是使用NSURLCache進(jìn)行的,因此,根據(jù)存儲策略進(jìn)行不同操作以便于下載圖片后進(jìn)行磁盤緩存
    4)之后就是從網(wǎng)絡(luò)下載圖片,這也要注意,作者是使用AFN進(jìn)行異步下載的、會生成UUID和之前一樣去檢驗下載后UUID是否發(fā)生改變;若成功,則將先緩存圖片,然后將AFImageDownloaderMergedTask中數(shù)組所有響應(yīng)取出并將圖片等信息通過回調(diào)返回;若失敗,也一樣進(jìn)行回調(diào)錯誤信息

注意:
1.作者默認(rèn)同時接受4個不同URL的請求,如果超過這個數(shù)量將把剩余的請求放入一個隊列中(使用數(shù)組實現(xiàn)的) 默認(rèn)是FIFO隊列,也可以設(shè)置LIFO隊列
2.當(dāng)一個Task完成后都會檢查該隊列是否有剩余的Task,有則取出來并執(zhí)行


- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                  withReceiptID:(nonnull NSUUID *)receiptID
                                                        success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                        failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
    __block NSURLSessionDataTask *task = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        NSString *URLIdentifier = request.URL.absoluteString;
        if (URLIdentifier == nil) {
            if (failure) {
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
                dispatch_async(dispatch_get_main_queue(), ^{
                    failure(request, nil, error);
                });
            }
            return;
        }

        // 1) Append the success and failure blocks to a pre-existing request if it already exists
        AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
        if (existingMergedTask != nil) {
            AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
            [existingMergedTask addResponseHandler:handler];
            task = existingMergedTask.task;
            return;
        }

        // 2) Attempt to load the image from the image cache if the cache policy allows it
        switch (request.cachePolicy) {
            case NSURLRequestUseProtocolCachePolicy:
            case NSURLRequestReturnCacheDataElseLoad:
            case NSURLRequestReturnCacheDataDontLoad: {
                UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
                if (cachedImage != nil) {
                    if (success) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            success(request, nil, cachedImage);
                        });
                    }
                    return;
                }
                break;
            }
            default:
                break;
        }

        // 3) Create the request and set up authentication, validation and response serialization
        NSUUID *mergedTaskIdentifier = [NSUUID UUID];
        NSURLSessionDataTask *createdTask;
        __weak __typeof__(self) weakSelf = self;

        createdTask = [self.sessionManager
                       dataTaskWithRequest:request
                       uploadProgress:nil
                       downloadProgress:nil
                       completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                           dispatch_async(self.responseQueue, ^{
                               __strong __typeof__(weakSelf) strongSelf = weakSelf;
                               AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
                               if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
                                   mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
                                   if (error) {
                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           if (handler.failureBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
                                               });
                                           }
                                       }
                                   } else {
                                       [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];

                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           if (handler.successBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
                                               });
                                           }
                                       }
                                       
                                   }
                               }
                               [strongSelf safelyDecrementActiveTaskCount];
                               [strongSelf safelyStartNextTaskIfNecessary];
                           });
                       }];

        // 4) Store the response handler for use when the request completes
        AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                                   success:success
                                                                                                   failure:failure];
        AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                                   initWithURLIdentifier:URLIdentifier
                                                   identifier:mergedTaskIdentifier
                                                   task:createdTask];
        [mergedTask addResponseHandler:handler];
        self.mergedTasks[URLIdentifier] = mergedTask;

        // 5) Either start the request or enqueue it depending on the current active request count
        if ([self isActiveRequestCountBelowMaximumLimit]) {
            [self startMergedTask:mergedTask];
        } else {
            [self enqueueMergedTask:mergedTask];
        }

        task = mergedTask.task;
    });
    if (task) {
        return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
    } else {
        return nil;
    }
}   

  1. 正如之前所說,作者自定義一些類如AFImageDownloadReceipt、AFImageDownloaderResponseHandlerd等都是用來存儲信息的,可以自行查看源代碼就會明白這些意思。這個代碼大致思路就是這個,還有取消下載、開始下載、還有AFHTTPSessionManager的配置可以查看源代碼進(jìn)行理解。

4) 我要查看一下SDWebImage的UIImageView的源代碼,看看他們有什么不同。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,390評論 4 61
  • 寫在開頭: 大概回憶下,之前我們講了AFNetworking整個網(wǎng)絡(luò)請求的流程,包括request的拼接,sess...
    涂耀輝閱讀 12,444評論 30 89
  • 哈哈哈,今天偷下懶,介紹一款小運(yùn)用天天打卡。它實用易用,功能專注,學(xué)習(xí)0成本。 使用這款運(yùn)用的起因是不想渾渾噩噩,...
    肥羅閱讀 662評論 0 0
  • 如果注定 如果注定能遇到相愛的人 我希望時間你能早點來 要不然 那炙熱的雙唇為誰柔軟 那輕聲的耳語為誰纏綿 那裹入...
    鮮衣怒馬_閱讀 205評論 0 1
  • 和同事一起吃食堂的時候,突然聊起了愛情。 大我五歲的姐姐說,她和現(xiàn)在的姐夫在一起挺不容易的,大學(xué)畢業(yè)女方家里不同意...
    文藝女青年專治各種不服閱讀 999評論 0 21

友情鏈接更多精彩內(nèi)容