iOS中给UIImageView设置网络图片时,通常用SDWebImage,但是AFNetworking也给了给UIImageView设置网络图片的方法,下面就具体看看其代码是怎么实现的.
实现主要有三个类实现的:AFAutoPurgingImageCache-内存缓存类,AFImageDownloader-图片下载类,UIImageView+AFNetworking - 设置网络图片UIImageView的分类.
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置header
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (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
{
// 当url为空时
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;
// 从缓存中获取图片
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;
}
}
- (void)cancelImageDownloadTask {
// 下载收据不为空时
if (self.af_activeImageDownloadReceipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
[self clearActiveDownloadInformation];
}
}
- (void)clearActiveDownloadInformation {
self.af_activeImageDownloadReceipt = nil;
}
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest {
return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
上面的代码的流程大致如下:
1.检查下的URL是否存在,不存,取消下载任务.
2.判断是否和当前的下载任务是否相同,如果相同直接返回.
3.检查缓存中是否有缓存,加载缓存.
4.如果没有缓存,就直接下载该图片.
从上面的代码可以看到UIImageView分类中添加了这个af_activeImageDownloadReceipt属性,不是分类中不能添加属性吗?确实分类中不能添加属性,因为在加载UIImageView类的时候,类的内存结构已经固定,但是可以通过runtime设置关联属性的方法,达到添加属性的功能,想MJRefresh中的mjfooter,mjheader都是通过关联属性实现的.
- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}
- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
这里用到了AFAutoPurgingImageCache实现内存缓存的功能,我们来看看其实现的细节:
- (instancetype)init {
// 设置内存缓存为100MB,最优内存容量为60MB
return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
if (self = [super init]) {
self.memoryCapacity = memoryCapacity;
self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
// 用NSMutableDictionary作为内存缓存
self.cachedImages = [[NSMutableDictionary alloc] init];
NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
// 并发队列
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
// 接受内存警告的通知
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(removeAllImages)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
我们可以看出这里是用NSMutableDictionary来作为内存存储图片,最大内存量为:100MB,最优内存量为:60MB,这里是最优内存存储量就是指,当内存量超过100MB,清理内存时,清理到60MB的内存量的水平.
内存缓存图片时,假如直接缓存图片的话,怎么标识图片的唯一性,如果内存使用量超过最大内存使用量时,怎么按照一定的淘汰规则(比如LRU)清理缓存等等,所以这里不能直接缓存图片,这里是缓存AFCachedImage对象.
@interface AFCachedImage : NSObject
// 图片
@property (nonatomic, strong) UIImage *image;
// 标识符
@property (nonatomic, strong) NSString *identifier;
// 大小
@property (nonatomic, assign) UInt64 totalBytes;
// 最后一次访问时间
@property (nonatomic, strong) NSDate *lastAccessDate;
// 当前内存缓存使用量
@property (nonatomic, assign) UInt64 currentMemoryUsage;
@end
@implementation AFCachedImage
-(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
if (self = [self init]) {
self.image = image;
self.identifier = identifier;
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
// 32位的颜色空间,4个字节组成
CGFloat bytesPerPixel = 4.0;
CGFloat bytesPerSize = imageSize.width * imageSize.height;
// 计算图片的大小
self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
// 最后一次访问时间
self.lastAccessDate = [NSDate date];
}
return self;
}
上面初始化一些属性,比如lastAccessDate,totalBytes等.
回到之前具体获得缓存的图片的实现
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
NSString *key = request.URL.absoluteString;
if (additionalIdentifier != nil) {
key = [key stringByAppendingString:additionalIdentifier];
}
return key;
}
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
// 设置最后一次获取时间
image = [cachedImage accessImage];
});
return image;
}
上面做的事就是根据key在self.cachedImages这个可变的字典中获取值,前面说了不是直接储存UIImage对象,是储存AFCachedImage对象,获取了AFCachedImage,要设置它最后一次访问时间.
再看下载图片的代码:
(NSURLCache *)defaultURLCache {
// 设置内存缓存为20MB,磁盘缓存为150MB
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:150 * 1024 * 1024
diskPath:@"com.alamofire.imagedownloader"];
}
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 设置cookis为yes
configuration.HTTPShouldSetCookies = YES;
// 设置https认证为No
configuration.HTTPShouldUsePipelining = NO;
// 设置缓存策略
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
// 设置允许蜂窝访问
configuration.allowsCellularAccess = YES;
// 设置超时时间为60s
configuration.timeoutIntervalForRequest = 60.0;
// 设置网络缓存
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
- (instancetype)init {
// 初始化默认session配置
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
// 初始一个sessionManager
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
// 设置响应序列化方式为图片响应序列化
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
// 设置下载的优先级为先进先出,最大下载数量为4个,内存缓存为AFAutoPurgingImageCache
return [self initWithSessionManager:sessionManager
downloadPrioritization:AFImageDownloadPrioritizationFIFO
maximumActiveDownloads:4
imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache {
if (self = [super init]) {
self.sessionManager = sessionManager;
self.downloadPrioritizaton = downloadPrioritization;
self.maximumActiveDownloads = maximumActiveDownloads;
self.imageCache = imageCache;
// 入队合并任务
self.queuedMergedTasks = [[NSMutableArray alloc] init];
// 合并任务
self.mergedTasks = [[NSMutableDictionary alloc] init];
// 活跃请求数量
self.activeRequestCount = 0;
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
// 串行队列
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
// 并发响应队列
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
+ (instancetype)defaultInstance {
// 设置AFImageDownloader单例
static AFImageDownloader *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (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;
// 如果URL为空直接返回
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;
}
// 如果当前下载任务,已经下载过
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
// 把failure和successblock生成一个handler加入到当前已存在的任务的responseHandlers数组中
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
// 如果内存缓存有,就从内存缓存加载图片
switch (request.cachePolicy) {
// 如果缓存策略为NSURLRequestUseProtocolCachePolicy,NSURLRequestReturnCacheDataElseLoad,NSURLRequestReturnCacheDataDontLoad三种的一种,即从缓存中加载图片
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;
}
// 创建下载请求
NSUUID *mergedTaskIdentifier = [NSUUID UUID];
NSURLSessionDataTask *createdTask;
__weak __typeof__(self) weakSelf = self;
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
// mergedTask的UUID和当前UUID相同
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
// 移除task
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
if (error) {
// 回调改task1的所有handler
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];
// 回调所有handler的successBlock
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];
});
}];
// 生成一个handler
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
// 生成一个mergedTask
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;
}
}
可以看出这里设置了URL级别的NSURLCache缓存,内存缓存为20MB,磁盘缓存为150MB,这里的内存缓存,和前面的内存缓存互不干扰,但是这里用了NSURLCache缓存,该缓存只对POS请求有效,请他请求方式就无能为力了.
创建的下载任务也不是马上就开始下载的,这里设置了最大下载数量为4个,如果当前下载数量小于4个就开始下载,否则根据下载的优先级,这里给出了两种优先级:
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
// 先进先出
AFImageDownloadPrioritizationFIFO,
// 后进先出
AFImageDownloadPrioritizationLIFO
};
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritizaton) {
case AFImageDownloadPrioritizationFIFO:
[self.queuedMergedTasks addObject:mergedTask];
break;
case AFImageDownloadPrioritizationLIFO:
[self.queuedMergedTasks insertObject:mergedTask atIndex:0];
break;
}
}
如果下载优先级为AFImageDownloadPrioritizationFIFO,将任务放在队列最后,如果优先级为AFImageDownloadPrioritizationLIFO,将任务放在第一个.
网友评论