UIImageView(AFNetworking)
这个文件是负责通过AFNetworking这个框架来下载图像的
它本身使用的主要的是AFImageDownloader负责下载图像,后面会说到
.m文件
设置类别的成员变量,关联,目的是引入AFImageDownloadReceipt的私有变量,在实现处还有一个关联,感兴趣的可以去看下源码
@implementation UIImageView (_AFNetworking)
- (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);
}
其中最主要的方法是setImage:对于相同的一个UIImageView如果设置了新的request就应当取消当前的下载
首先判断request是否为nil 取消之前的下载,设为placeHolder,return
判断当前的request是否已添加或活跃,是return
取消之前的下载,情况上面说明
开始查找缓存,找到了就设置,没找到就开是下载,关于缓存的部分请往下看
- (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;
}
}
AFImageDownloader文件
.h中的枚举来定义优先队列的类型
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
AFImageDownloadPrioritizationFIFO,默认的
AFImageDownloadPrioritizationLIFO
};
.m文件中有3个内部类
AFImageDownloadReceipt有2个属性:
UUID(和AFImageDownloaderResponseHandler的UUID保持一致) 目的是为了能够取消【取消的实质是未执行的dataTask,先判断来查找此task的completionBlock,在处置task,当剩下的block为0个时,cancel此block】
dataTask
AFImageDownloaderResponseHandler有3个属性:
UUID
successBlock
failureBlock
AFImageDownloaderMergedTask有3个属性
NSString类型identifier
dataTask
NSMutableArray<AFImageDownloaderResponseHandler> 作为responseHandler
AFImageDownloader
AFImageRequestCache 处理图像缓存
AFHTTPSessionManager创建dataTask
AFImageDownloadPrioritization默认是FIFO
此外还有一些私有变量:
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;//串行的,用于同步一些内容,但是task得管理本身还是依据session的并发下载
@property (nonatomic, strong) dispatch_queue_t responseQueue;//并发的用于读取cache内容
@property (nonatomic, assign) NSInteger maximumActiveDownloads;//最大下载数
@property (nonatomic, assign) NSInteger activeRequestCount;//当前下载数
@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;//当前task数组
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;//用identifier做key,mergedTask做ob
此类的重点是3个函数
1.initWithSessionManager
- (instancetype)init {
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
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 {
static AFImageDownloader *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
2.下载任务
以下操作是阻塞当前线程在串行队列中:
第一步:查找是否存在request【使用的request.url.absolluteString】,如果存在【未执行】则在相应的mergedTask中加入对应的handler,return
第二步:从缓存中加载图片,如果存在则在主队列调用success回调,return
第三步:创建dataTask,并在完成时异步调用其本身的所有绑定的handler和增改activieCount值和看是否有待处理task进行resume
第四步:对刚刚创建的dataTask进行登记并记录mergedTask相关信息
第五步:根据最大并发数来决定当前的task是否resume
- (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 *identifier = request.URL.absoluteString;
// 1) Append the success and failure blocks to a pre-existing request if it already exists
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[identifier];
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
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 = [strongSelf safelyRemoveMergedTaskWithIdentifier:identifier];
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]
initWithIdentifier:identifier
task:createdTask];
[mergedTask addResponseHandler:handler];
self.mergedTasks[identifier] = 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;
}
}
3.Cancel Task
以下操作是阻塞当前线程在串行队列中:
根据imageDownloadReceipt找到mergedTask,根据UUID【这个是MergedTask下面的小分支:也就是说MergedTask本身使用的是identifier,它本身的handler的添加使用的是uuid】找到当前待取消的handler的索引,同时异步执行failure,此外需要判断当前的mergedTask是否没有handler,如果没有handler那么cancel此Task
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
NSString *identifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[identifier];
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
return handler.uuid == imageDownloadReceipt.receiptID;
}];
if (index != NSNotFound) {
AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
[mergedTask removeResponseHandler:handler];
NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[mergedTask.task cancel];
}
});
}
在图像内容存储上使用了AFAutoPurgingImageCache.h,这个类本身的存储使用的字典来存储内容,通过此类来管理内容的大小
.h中有2个协议
AFImageCache:提供对图像使用identifier的访问
AFImageRquestCache<AFImageCache>:在外层封装引入request,来支持identifier【默认值是request.url.absoluteString】
.m中
一个内部类AFCachedImage来记录当前的缓存的图像信息,其中一个主要的是lastAccessDate,这个用于purge时的排序,类似内存管理的最不访问的净化
MutableDictionary<String,AFCachedImage>
同步队列【并发的】,使用dispatch_syn支持同步,使用barrier_asyn来支持同步操作
几个重要的函数
init:初始化内存大小和添加对内存警告的通知【释放所有图像缓存】,最大存储默认是100M,优选的净化存储是60M
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
if (self = [super init]) {
self.memoryCapacity = memoryCapacity;
self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
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;
}
addImage:
首先替换掉之前的图像
然后优化当前的内存容量:根据超出
preferredMemoryUsageAfterPurge
的大小设置优化,方式就是lastAccess进行升序,然后删除dict中的缓存图像
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
dispatch_barrier_async(self.synchronizationQueue, ^{
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
if (previousCachedImage != nil) {
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
self.cachedImages[identifier] = cacheImage;
self.currentMemoryUsage += cacheImage.totalBytes;
});
dispatch_barrier_async(self.synchronizationQueue, ^{
if (self.currentMemoryUsage > self.memoryCapacity) {
UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
ascending:YES];
[sortedImages sortUsingDescriptors:@[sortDescriptor]];
UInt64 bytesPurged = 0;
for (AFCachedImage *cachedImage in sortedImages) {
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
if (bytesPurged >= bytesToPurge) {
break ;
}
}
self.currentMemoryUsage -= bytesPurged;
}
});
}