UIImageView
在使用AFNetworking显示网络图片的时候,用到的最关键的方法
- (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
{
//1、首先,判断请求的图片地址是否有效,无效的话,取消图片下载任务,并将placeholderImage赋值,返回
if ([urlRequest URL] == nil) {
[self cancelImageDownloadTask];
self.image = placeholderImage;
return;
}
//2、接着再判断当前下载请求是否和urlRequest下载一样,一样就返回
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
return;
}
//3、取消之前的下载任务
[self cancelImageDownloadTask];
//4、获取downloader,并获取downloader的imageCache图片缓存
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
id <AFImageRequestCache> imageCache = downloader.imageCache;
//5、如果图片缓存存在,就使用该缓存图片 Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
if (cachedImage) {
//5.1缓存图片存在的情况下,判断是否设置了success,设置了就调用block返回urlRequest和缓存图片,否则将缓存图片赋值给self.image。
if (success) {
success(urlRequest, nil, cachedImage);
} else {
self.image = cachedImage;
}
[self clearActiveDownloadInformation];
} else {
if (placeholderImage) {
self.image = placeholderImage;
}
//6、如果图片缓存不存在,开始下载图片
__weak __typeof(self)weakSelf = self;
NSUUID *downloadID = [NSUUID UUID];
//6.1、从该处可以发现,现在使用一个DownloadReceipt进行图片下载
//通过[NSUUID UUID]来生成一个唯一的标识downloadID证明AFImageDownloadReceipt的身份。
AFImageDownloadReceipt *receipt;
//6.2、在AFImageDownloader的downloadImageForURLRequest方法里,处理了图片下载的过程
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、每次使用setImageWithURLRequest加载图片时,首先,判断请求的图片地址是否有效,无效的话,取消图片下载任务,将af_activeImageDownloadReceipt凭据置为nil,并将placeholderImage赋值给self.image,返回。
2、接着再判断当前下载请求是否和urlRequest下载一样,一样就返回
3、取消之前的下载任务
4、如果图片缓存存在,就使用该缓存图片;不存在就使用AFImageDownloader中的downloadImageForURLRequest方法请求网络图片,请求成功后,将请求到的图片赋值,并将将af_activeImageDownloadReceipt置为nil。
AFImageDownloader
接着,再来看下在图片下载过程中AFImageDownloader这个类进行了哪些操作。
/**
The AFImageDownloadReceipt
is an object vended by the AFImageDownloader
when starting a data task. It can be used to cancel active tasks running on the AFImageDownloader
session. As a general rule, image data tasks should be cancelled using the AFImageDownloadReceipt
instead of calling cancel
directly on the task
itself. The AFImageDownloader
is optimized to handle duplicate task scenarios as well as pending versus active downloads.
*/
源码注释中写到,在开始图片数据任务时,AFImageDownloadReceipt是由AFImageDownloader提供的一个对象。AFImageDownloadReceipt用来取消运行在AFImageDownloader session中的现行任务。图片数据任务应该通过使用AFImageDownloadReceipt来取消,而不是直接cancel掉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 = strongSelf.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 {
if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
[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;
}
}
流程:
1、首先创建NSURLSessionDataTask
__block NSURLSessionDataTask *task = nil;
2、然后需要验证AFImageDownloadReceipt,因为该方法最后返回的对象是AFImageDownloadReceipt类型的。
而AFImageDownloadReceipt对象中又有(NSURLSessionDataTask *task、NSUUID *receiptID)这两个属性。
于是,可以使用AFImageDownloadReceipt中的receiptID来证明它的身份。用request.URL.absoluteString,即请求图片的网络地址作为唯一标识符URLIdentifier。
验证URLIdentifier,如果不存在并且回调的failure为true,则在主线程dispatch_get_main_queue中回调请求的错误。
3、如果验证通过,继续设置task。
首先查看self.mergedTasks合并任务中是否存在key为URLIdentifier的任务。如果存在,则为这个existingMergedTask添加一个相应处理回调的AFImageDownloaderResponseHandler。然后跳出dispatch_sync这个线程。如果不存在,继续执行后面的代码。
(因为方法最后会根据URLIdentifier,将mergedTask放进字典self.mergedTasks 里面 AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;)
4、如果self.mergedTasks中没有这个task,需要先检查下内存中是否有这个链接的图片缓存存在,如果存在,回调success。
// 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];//关于如何在内存中查找缓存图片,需要看AFAutoPurgingImageCache这个类。
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
5、如果内存中不存在图片,则需要开始创建NSURLSessionDataTask,请求图片网络资源。
NSURLSessionDataTask *createdTask;
// 创建task
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 5.1因为请求数据的任务都放进AFImageDownloaderMergedTask中,用图片地址作为key关联,所以,在请求之前,先从self.mergedTask中获取URLIdentifier对于的task
// 5.2检查URLIdentifier和mergedTaskIdentifier对应的mergedTask是否是同一个,如果是,则从self.mergedTasks[URLIdentifier]中取出 mergedTask后,移除self.mergedTasks字典中的key为URLIdentifier对应的对象(因为该URLIdentifier地址的图片网络请求已经完成,这个task可以从self.mergedTasks中移除)
// 5.3如果有error,处理错误block,
如果没有error,将图片放进缓存中,然后遍历mergedTask.responseHandlers中的successBlock
// 5.4将当前的activeRequestCount请求数减一
// 5.4 resume开启 self.queuedMergedTasks中的下一个task
}];
});
6、设置self.mergedTasks[URLIdentifier]
// 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;
7、判断当前请求task的数量是否在最大请求数范围内,如果在范围内,发起请求,如果不在范围内,根据task的优先级对其进行排序
// 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];
}
8、最后返回这个AFImageDownloadReceipt
关于使用AFNetworking的UIImageView经常被问的一个问题,
关于同一个网络地址图片,多处使用,如何处理?(比如3个cell,都显示同一个url的图片)
解释:
AFImageDownloader中有一个属性,NSMutableDictionary mergedTasks用来存储合并任务的。当有图片请求的时候,首先查看self.mergedTasks合并任务中是否存在key为URLIdentifier的任务。如果存在,则为这个existingMergedTask添加一个相应处理回调的AFImageDownloaderResponseHandler。等资源下载完成后回调图片。(AFImageDownloaderMergedTask对象有一个属性NSMutableArray <AFImageDownloaderResponseHandler> *responseHandlers;数组用来存放同一url需要的回调处理)
【相当于根据url的key值创建对应的existingMergedTask,同一url对应一个task, 有多少次调用就为这个task增加多少个回调,每一个回调放主线程dispatch_get_main_queue中进行】