前言
最近在使用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在加載圖片時的延遲問題)
- 相關(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)流程
- 當(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)中說了,內(nèi)存緩存沒有使用請求和UUID去下載圖片,那么現(xiàn)在研究其下載操作
這段代碼有點長,耐心看一下
去下載圖片時的思路:
- 檢查請求的URL是否為空,失敗則則返回信息(感覺這一句有點多余,因為調(diào)用這個方法之間已經(jīng)檢查了URL是否為空)
- 在Task沒有執(zhí)行時,多個請求可能會同時達(dá)到該方法,因此,需要一個字典存儲該URL對應(yīng)的AFImageDownloaderMergedTask,若有,則取出并創(chuàng)建一個AFImageDownloaderResponseHandler
放入該自定義Task的一個響應(yīng)數(shù)組中。并將該自定義task的NSURLSessionDataTask賦值給task;若沒有,則進(jìn)行下一步 - 由于對于磁盤緩存作者是使用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;
}
}
- 正如之前所說,作者自定義一些類如AFImageDownloadReceipt、AFImageDownloaderResponseHandlerd等都是用來存儲信息的,可以自行查看源代碼就會明白這些意思。這個代碼大致思路就是這個,還有取消下載、開始下載、還有AFHTTPSessionManager的配置可以查看源代碼進(jìn)行理解。
4) 我要查看一下SDWebImage的UIImageView的源代碼,看看他們有什么不同。