參考:AFNetworking 3.0 源碼解讀(八)之 AFImageDownloader
說明:很多內(nèi)容都是摘抄原文,只是根據(jù)自己的需要進行摘抄或者總結(jié),如有不妥請及時指出,謝謝。
AFImageDownloader 這個類對寫DownloadManager有很大的借鑒意義。在平時的開發(fā)中,當我們使用UIImageView加載一個網(wǎng)絡(luò)上的圖片時,其原理就是把圖片下載下來,然后再賦值。這也是AFImageDownloader這個類的核心功能。
AFImageDownloadPrioritization
表示圖片下載的優(yōu)先級:
AFImageDownloadPrioritizationFIFO 表示先進先出
AFImageDownloadPrioritizationLIFO 表示后進先出
AFImageDownloaderMergedTask
AFImageDownloaderMergedTask 封裝了對同一個請求的處理。同下載任務(wù)一樣,也存在下載的URL重復(fù)的情況,這種情況需要做特殊處理。這里是一個值得借鑒的地方。那么如何判定是一個請求呢?AFImageDownloader是通過request.URL.absoluteString來進行判斷的。
AFImageDownloader
NSURLCache
我們簡單介紹下NSURLCache。NSURLCache 為您的應(yīng)用的 URL 請求提供了內(nèi)存中以及磁盤上的綜合緩存機制。網(wǎng)絡(luò)緩存減少了需要向服務(wù)器發(fā)送請求的次數(shù),同時也提升了離線或在低速網(wǎng)絡(luò)中使用應(yīng)用的體驗。當一個請求完成下載來自服務(wù)器的回應(yīng),一個緩存的回應(yīng)將在本地保存。下一次同一個請求再發(fā)起時,本地保存的回應(yīng)就會馬上返回,不需要連接服務(wù)器。NSURLCache 會 自動 且 透明 地返回回應(yīng)。
NSURLRequest 有個 cachePolicy 屬性,我們平時最常用的有四個屬性:
NSURLRequestUseProtocolCachePolicy: 對特定的 URL 請求使用網(wǎng)絡(luò)
協(xié)議中實現(xiàn)的緩存邏輯。這是默認的策略。
NSURLRequestReloadIgnoringLocalCacheData:數(shù)據(jù)需要從原始地址
加載。不使用現(xiàn)有緩存。
NSURLRequestReturnCacheDataElseLoad:無論緩存是否過期,先使
用本地緩存數(shù)據(jù)。如果緩存中沒有請求所對應(yīng)的數(shù)據(jù),那么從原始地址
加載數(shù)據(jù)
NSURLRequestReturnCacheDataDontLoad:無論緩存是否過期,先使
用本地緩存數(shù)據(jù)。如果緩存中沒有請求所對應(yīng)的數(shù)據(jù),那么放棄從原始
地址加載數(shù)據(jù),請求視為失?。矗骸半x線”模式)。
核心方法
- (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;
//判斷URLIdentifier有效性
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
//根據(jù)URLIdentifier,找到已經(jīng)存在的任務(wù),把回掉與這個任務(wù)進行綁定
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
//走到這一步,說明圖片還沒有找到,那么必然我們需要下載了
//創(chuàng)建request,設(shè)置權(quán)限、序列化信息
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 = strongSelf.mergedTasks[URLIdentifier];
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
//從合并任務(wù)中,找到自己,并進行移除
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 {
//移除成功后,對圖片進行緩存
if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
[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);
});
}
}
}
}
//更新任務(wù)數(shù),并開始下個任務(wù)
[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];
//把當前任務(wù)添加到合并隊列中
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
//如果當前任務(wù)數(shù)沒有超出最大上限,則立即啟動任務(wù)
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
//按照優(yōu)先級對任務(wù)進行排列
[self enqueueMergedTask:mergedTask];
}
task = mergedTask.task;
});
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
}
核心思想:數(shù)據(jù)共用,各自處理各自的響應(yīng)結(jié)果。
對核心思想細化:
1、找到還未執(zhí)行完的相同任務(wù),如果找到了,就綁定回掉事件,等待任務(wù)執(zhí)行即可。
2、如果緩存策略允許,直接去查圖片,返回成功
3、創(chuàng)建一個下載任務(wù),并添加到等待隊列中
4、如果當前執(zhí)行的任務(wù)數(shù)沒有超過并發(fā)數(shù),則立即執(zhí)行,否則按照優(yōu)先級決定任務(wù)放在隊前還是隊尾
5、執(zhí)行任務(wù)
5.1、從合并任務(wù)中找到自己,并移除
5.2、對圖片按照緩存策略進行緩存
5.3、更新激活的任務(wù)數(shù)并開始下一個任務(wù)
這個核心思想與上一片中的圖片緩存策略合并起來,就是圖片緩存原理的一個雛形了