前言
最近在使用SDWebImage時(shí)突然想到AFNetworking也有類(lèi)似于SD中UIImageView+WebCache的功能涛舍,因此澄惊,查看了AF中相關(guān)代碼。
UIImageView+AFNetworking簡(jiǎn)單使用
- (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)作用
- 用于解決異步加載圖片時(shí)的卡頓問(wèn)題(例如肛搬,UITableViewCell在加載圖片時(shí)的延遲問(wèn)題)
- 相關(guān)的頭文件和.m文件
① UIImageView+AFNetworking.h / UIImageView+AFNetworking.m(用戶(hù)接觸的類(lèi),用于取消毕贼、下載圖片等操作)
② AFImageDownloader.h / AFImageDownloader.m(真正實(shí)現(xiàn)取消温赔、下載圖片等操作)
③ AFAutoPurgingImageCache.h / AFAutoPurgingImageCache.m(該框架內(nèi)部實(shí)現(xiàn)的一個(gè)內(nèi)容緩存類(lèi))
3)公共接口
- 由于各個(gè)文件的接口有很多,這里只列出部分重要的接口帅刀,剩余的接口可以參考源代碼:AFNetworking
① UIImageView+AFNetworking文件全部公共接口
/**
* 用于設(shè)置AFImageDownloader下載器(用到了關(guān)聯(lián)對(duì)象)
*/
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;
/**
* 用于獲取AFImageDownloader下載器(用到了關(guān)聯(lián)對(duì)象)
*/
+ (AFImageDownloader *)sharedImageDownloader;
// 傳遞URL來(lái)設(shè)置UIImageView的圖片
- (void)setImageWithURL:(NSURL *)url;
// 傳遞URL設(shè)置UIImageView的圖片让腹,同時(shí),當(dāng)圖片沒(méi)有下載完時(shí)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的單例對(duì)象
+ (instancetype)defaultInstance;
// 獲取默認(rèn)的NSURLCache
+ (NSURLCache *)defaultURLCache;
// 重新實(shí)現(xiàn)init方法
- (instancetype)init;
// 初始化AFHTTPSessionManager對(duì)象及其該分類(lèi)相關(guān)的屬性
- (instancetype)initWithSessionManager (AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(nullable id <AFImageRequestCache>)imageCache;
// 通過(guò)請(qǐng)求下載圖片
- (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;
// 通過(guò)請(qǐng)求下載圖片骇窍,同時(shí),指定了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文件
// 該類(lèi)實(shí)現(xiàn)內(nèi)存緩存锥余,是通過(guò)NSDictionary來(lái)實(shí)現(xiàn)的
/**
聲明了一系列將圖片放入緩存腹纳、從緩存移除圖片以及獲取緩存中圖片的API
*/
@protocol AFImageCache <NSObject>
/**
將圖片放入緩存identifier是字典的key值,用于存儲(chǔ)key對(duì)應(yīng)的UIImage
*/
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;
/**
通過(guò)identifier(也就是Key值)將對(duì)應(yīng)的的UIImage從緩存(字典)移除
*/
- (BOOL)removeImageWithIdentifier:(NSString *)identifier;
/**
很黃很暴力驱犹,一次性將緩存清空
*/
- (BOOL)removeAllImages;
/**
通過(guò)identifier獲取圖片
*/
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;
@end
--- --
/**
該協(xié)議是對(duì)AFImageCache的擴(kuò)充嘲恍,主要是將請(qǐng)求的URL和后面identifier替代之前AFImageCache之前的identifier。方法的內(nèi)部實(shí)現(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
/**
實(shí)現(xiàn)內(nèi)存緩存的具體類(lèi)
*/
@interface AFAutoPurgingImageCache : NSObject <AFImageRequestCache>
/**
初始化該緩存類(lèi)雄驹,默認(rèn)情況下memoryCapicty大小是100M佃牛,而圖片的緩存大小大于memoryCapicty時(shí),會(huì)清除圖片直到60M停止
*/
- (instancetype)init;
/**
一次性設(shè)置緩存的內(nèi)存大小
① memoryCapacity :緩存區(qū)大小
② preferredMemoryCapaciry:圖片緩存超出緩存區(qū)大小后清除圖片后合適的剩余緩存大小
*/
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;
@end
具體實(shí)現(xiàn)流程
- 當(dāng)我們想個(gè)ImageView的圖片進(jìn)行賦值時(shí)医舆,我們會(huì)調(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];
}
// 最終實(shí)現(xiàn)地方
// 該方法思路:
/**
* 1) 判斷傳入的請(qǐng)求中URL是否為空;若為空蔬将,則取消下載爷速,imageView顯示占位圖片,否則霞怀,進(jìn)入下一步判斷
* 2) 判斷當(dāng)前的請(qǐng)求是否有task處于活動(dòng)狀態(tài)(意思可能用戶(hù)單位時(shí)間內(nèi)多次發(fā)了同一個(gè)請(qǐng)求惫东,而在這個(gè)請(qǐng)求發(fā)送時(shí)已經(jīng)有下載的Task執(zhí)行了,這個(gè)請(qǐng)求將結(jié)束)
* 3) 根據(jù)請(qǐng)求查看內(nèi)存緩存中是否目前有當(dāng)前請(qǐng)求的圖片毙石,若有則查看success有無(wú)回調(diào)廉沮,有回調(diào)則將圖片回調(diào)回去,否則徐矩,直接設(shè)置本ImageView的image
* 4) 若本地內(nèi)存緩存沒(méi)有數(shù)據(jù)废封,則先將imageView的image設(shè)置為占位圖片并生成UUID,然后將請(qǐng)求和UUID發(fā)送出去; 若成功丧蘸,則檢查UUID是否和之前生成的UUID相等,以防止
* 數(shù)據(jù)出錯(cuò),沒(méi)有問(wèn)題則根據(jù)success是否有回調(diào)將圖片發(fā)送出去;若失敗力喷,也檢查UUID是否相等刽漂,并根據(jù)有無(wú)failure回調(diào),將錯(cuò)誤信息輸出
*
*/
③ - (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)中說(shuō)了弟孟,內(nèi)存緩存沒(méi)有使用請(qǐng)求和UUID去下載圖片贝咙,那么現(xiàn)在研究其下載操作
這段代碼有點(diǎn)長(zhǎng),耐心看一下
去下載圖片時(shí)的思路:
- 檢查請(qǐng)求的URL是否為空拂募,失敗則則返回信息(感覺(jué)這一句有點(diǎn)多余庭猩,因?yàn)檎{(diào)用這個(gè)方法之間已經(jīng)檢查了URL是否為空)
- 在Task沒(méi)有執(zhí)行時(shí),多個(gè)請(qǐng)求可能會(huì)同時(shí)達(dá)到該方法陈症,因此蔼水,需要一個(gè)字典存儲(chǔ)該URL對(duì)應(yīng)的AFImageDownloaderMergedTask,若有,則取出并創(chuàng)建一個(gè)AFImageDownloaderResponseHandler
放入該自定義Task的一個(gè)響應(yīng)數(shù)組中录肯。并將該自定義task的NSURLSessionDataTask賦值給task;若沒(méi)有趴腋,則進(jìn)行下一步 - 由于對(duì)于磁盤(pán)緩存作者是使用NSURLCache進(jìn)行的,因此论咏,根據(jù)存儲(chǔ)策略進(jìn)行不同操作以便于下載圖片后進(jìn)行磁盤(pán)緩存
4)之后就是從網(wǎng)絡(luò)下載圖片优炬,這也要注意,作者是使用AFN進(jìn)行異步下載的厅贪、會(huì)生成UUID和之前一樣去檢驗(yàn)下載后UUID是否發(fā)生改變蠢护;若成功,則將先緩存圖片养涮,然后將AFImageDownloaderMergedTask中數(shù)組所有響應(yīng)取出并將圖片等信息通過(guò)回調(diào)返回;若失敗葵硕,也一樣進(jìn)行回調(diào)錯(cuò)誤信息
注意:
1.作者默認(rèn)同時(shí)接受4個(gè)不同URL的請(qǐng)求,如果超過(guò)這個(gè)數(shù)量將把剩余的請(qǐng)求放入一個(gè)隊(duì)列中(使用數(shù)組實(shí)現(xiàn)的) 默認(rèn)是FIFO隊(duì)列单寂,也可以設(shè)置LIFO隊(duì)列
2.當(dāng)一個(gè)Task完成后都會(huì)檢查該隊(duì)列是否有剩余的Task贬芥,有則取出來(lái)并執(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;
}
}
- 正如之前所說(shuō),作者自定義一些類(lèi)如AFImageDownloadReceipt宣决、AFImageDownloaderResponseHandlerd等都是用來(lái)存儲(chǔ)信息的蘸劈,可以自行查看源代碼就會(huì)明白這些意思。這個(gè)代碼大致思路就是這個(gè)尊沸,還有取消下載威沫、開(kāi)始下載、還有AFHTTPSessionManager的配置可以查看源代碼進(jìn)行理解洼专。
4) 我要查看一下SDWebImage的UIImageView的源代碼棒掠,看看他們有什么不同。