前言
最近在使用SDWebImage时突然想到AFNetworking也有类似于SD中UIImageView+WebCache的功能,因此,查看了AF中相关代码。
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在加载图片时的延迟问题)
- 相关的头文件和.m文件
① UIImageView+AFNetworking.h / UIImageView+AFNetworking.m(用户接触的类,用于取消、下载图片等操作)
② AFImageDownloader.h / AFImageDownloader.m(真正实现取消、下载图片等操作)
③ AFAutoPurgingImageCache.h / AFAutoPurgingImageCache.m(该框架内部实现的一个内容缓存类)
3)公共接口
- 由于各个文件的接口有很多,这里只列出部分重要的接口,剩余的接口可以参考源代码:AFNetworking
① UIImageView+AFNetworking文件全部公共接口
/**
* 用于设置AFImageDownloader下载器(用到了关联对象)
*/
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;
/**
* 用于获取AFImageDownloader下载器(用到了关联对象)
*/
+ (AFImageDownloader *)sharedImageDownloader;
// 传递URL来设置UIImageView的图片
- (void)setImageWithURL:(NSURL *)url;
// 传递URL设置UIImageView的图片,同时,当图片没有下载完时UIImageView只显示占位图
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
// 传递URL和占位图,并且下载图片成功或者失败后有回调
- (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;
// 取消下载的任务
- (void)cancelImageDownloadTask;
② AFImageDownloader文件
// 获取AFImageDownloader的单例对象
+ (instancetype)defaultInstance;
// 获取默认的NSURLCache
+ (NSURLCache *)defaultURLCache;
// 重新实现init方法
- (instancetype)init;
// 初始化AFHTTPSessionManager对象及其该分类相关的属性
- (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文件
// 该类实现内存缓存,是通过NSDictionary来实现的
/**
声明了一系列将图片放入缓存、从缓存移除图片以及获取缓存中图片的API
*/
@protocol AFImageCache <NSObject>
/**
将图片放入缓存identifier是字典的key值,用于存储key对应的UIImage
*/
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;
/**
通过identifier(也就是Key值)将对应的的UIImage从缓存(字典)移除
*/
- (BOOL)removeImageWithIdentifier:(NSString *)identifier;
/**
很黄很暴力,一次性将缓存清空
*/
- (BOOL)removeAllImages;
/**
通过identifier获取图片
*/
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;
@end
--- --
/**
该协议是对AFImageCache的扩充,主要是将请求的URL和后面identifier替代之前AFImageCache之前的identifier。方法的内部实现还是使用了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
/**
实现内存缓存的具体类
*/
@interface AFAutoPurgingImageCache : NSObject <AFImageRequestCache>
/**
初始化该缓存类,默认情况下memoryCapicty大小是100M,而图片的缓存大小大于memoryCapicty时,会清除图片直到60M停止
*/
- (instancetype)init;
/**
一次性设置缓存的内存大小
① memoryCapacity :缓存区大小
② preferredMemoryCapaciry:图片缓存超出缓存区大小后清除图片后合适的剩余缓存大小
*/
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;
@end
具体实现流程
- 当我们想个ImageView的图片进行赋值时,我们会调用以下代码中的一种:
// 调用了②,占位图片为空
① - (void)setImageWithURL:(NSURL *)url {
[self setImageWithURL:url placeholderImage:nil];
}
// 调用了③ success和failure回调为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];
}
// 最终实现地方
// 该方法思路:
/**
* 1) 判断传入的请求中URL是否为空;若为空,则取消下载,imageView显示占位图片,否则,进入下一步判断
* 2) 判断当前的请求是否有task处于活动状态(意思可能用户单位时间内多次发了同一个请求,而在这个请求发送时已经有下载的Task执行了,这个请求将结束)
* 3) 根据请求查看内存缓存中是否目前有当前请求的图片,若有则查看success有无回调,有回调则将图片回调回去,否则,直接设置本ImageView的image
* 4) 若本地内存缓存没有数据,则先将imageView的image设置为占位图片并生成UUID,然后将请求和UUID发送出去; 若成功,则检查UUID是否和之前生成的UUID相等,以防止
* 数据出错,没有问题则根据success是否有回调将图片发送出去;若失败,也检查UUID是否相等,并根据有无failure回调,将错误信息输出
*
*/
③ - (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)中说了,内存缓存没有使用请求和UUID去下载图片,那么现在研究其下载操作
这段代码有点长,耐心看一下
去下载图片时的思路:
- 检查请求的URL是否为空,失败则则返回信息(感觉这一句有点多余,因为调用这个方法之间已经检查了URL是否为空)
- 在Task没有执行时,多个请求可能会同时达到该方法,因此,需要一个字典存储该URL对应的AFImageDownloaderMergedTask,若有,则取出并创建一个AFImageDownloaderResponseHandler
放入该自定义Task的一个响应数组中。并将该自定义task的NSURLSessionDataTask赋值给task;若没有,则进行下一步 - 由于对于磁盘缓存作者是使用NSURLCache进行的,因此,根据存储策略进行不同操作以便于下载图片后进行磁盘缓存
4)之后就是从网络下载图片,这也要注意,作者是使用AFN进行异步下载的、会生成UUID和之前一样去检验下载后UUID是否发生改变;若成功,则将先缓存图片,然后将AFImageDownloaderMergedTask中数组所有响应取出并将图片等信息通过回调返回;若失败,也一样进行回调错误信息
注意:
1.作者默认同时接受4个不同URL的请求,如果超过这个数量将把剩余的请求放入一个队列中(使用数组实现的) 默认是FIFO队列,也可以设置LIFO队列
2.当一个Task完成后都会检查该队列是否有剩余的Task,有则取出来并执行
- (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的配置可以查看源代码进行理解。
4) 我要查看一下SDWebImage的UIImageView的源代码,看看他们有什么不同。