AFN AFImageDownloader 源碼分析
源碼也是很簡(jiǎn)單,再次來(lái)學(xué)人家的思想,其實(shí)讀源碼最終就是學(xué)習(xí)人家的設(shè)計(jì)和思想,到最后你會(huì)變成一個(gè)特別喜歡讀源碼的人
AFImageDownloaderResponseHandler
我們正常在設(shè)計(jì)接口的時(shí)候,都會(huì)留給別人一個(gè)成功或者失敗的回調(diào),但有沒(méi)有想過(guò)一個(gè)問(wèn)題,比如我一個(gè)操作調(diào)用多次,但這個(gè)操作是重復(fù)的,但我只關(guān)心成功或者失敗的回調(diào),那么需要怎么辦?或者換句話說(shuō),我們平時(shí)調(diào)用代理的時(shí)候,設(shè)置代理,都是設(shè)置單一代理,那么這個(gè)代理,最終只會(huì)被回到一次到一個(gè)地方,我們也是會(huì)有需求,設(shè)置多個(gè)代理,那么怎么做呢?
這里作者將一個(gè)接口中的成功或者失敗額回調(diào),抽象到一個(gè)具體的類里面去保存,而之后將這個(gè)類保存到一個(gè)字典中,每次的key不同,就能實(shí)現(xiàn),多次回調(diào)block或者代理的需求了
AFImageDownloaderMergedTask
這個(gè)類就是對(duì)下載圖片task的一個(gè)封裝
@interface AFImageDownloaderMergedTask : NSObject
@property (nonatomic, strong) NSString *URLIdentifier;
@property (nonatomic, strong) NSUUID *identifier;
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
@end
可以看到他保存著請(qǐng)求的url,id標(biāo)識(shí),任務(wù)task,和上面所說(shuō)的回調(diào)數(shù)組,也就是說(shuō)一個(gè)task可以有多個(gè)response回調(diào)block
AFImageDownloader
+ (NSURLCache *)defaultURLCache {
// 這篇文章不錯(cuò):https://www.cnblogs.com/madpanda/p/4700741.html
NSUInteger memoryCapacity = 20 * 1024 * 1024; // 20MB
NSUInteger diskCapacity = 150 * 1024 * 1024; // 150MB
NSURL *cacheURL = [[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil]
URLByAppendingPathComponent:@"com.alamofire.imagedownloader"];
#if TARGET_OS_MACCATALYST
return [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity
diskCapacity:diskCapacity
directoryURL:cacheURL];
#else
return [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity
diskCapacity:diskCapacity
diskPath:[cacheURL path]];
#endif
}
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//TODO set the default HTTP headers
configuration.HTTPShouldSetCookies = YES;
configuration.HTTPShouldUsePipelining = NO;
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
configuration.allowsCellularAccess = YES;
configuration.timeoutIntervalForRequest = 60.0;
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
這里會(huì)造一個(gè)NSURLCache的類,用于緩存,主要是配置個(gè)系統(tǒng)的NSURLSessionConfiguration這個(gè)類,是依據(jù)這個(gè)屬性的requestCachePolicy,也就是說(shuō)緩存策略,是否從緩存中讀取,如果這個(gè)策略中選擇了從緩存中讀取,那么我們下載完圖片之后會(huì)存放在緩存中,下次同一個(gè)請(qǐng)求,就可以不請(qǐng)求了,直接從緩存中讀取圖片就可以了,后面會(huì)講到,,這里可以看到,申請(qǐng)內(nèi)存緩存20M,磁盤(pán)緩存250M,
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache {
if (self = [super init]) {
self.sessionManager = sessionManager;
// 下載策略,先進(jìn)先出,還是先進(jìn)后出
self.downloadPrioritization = downloadPrioritization;
// 允許最大下載個(gè)數(shù)
self.maximumActiveDownloads = maximumActiveDownloads;
// 圖片緩存實(shí)現(xiàn),AFN提供了緩存策略,也可以自己實(shí)現(xiàn)
self.imageCache = imageCache;
// 隊(duì)列中的任務(wù)
self.queuedMergedTasks = [[NSMutableArray alloc] init];
self.mergedTasks = [[NSMutableDictionary alloc] init];
self.activeRequestCount = 0;
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
// 事件觸發(fā)同步隊(duì)列
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
// 響應(yīng)隊(duì)列
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (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;
// 進(jìn)入到同步串行隊(duì)列
dispatch_sync(self.synchronizationQueue, ^{
// url 為唯一標(biāo)識(shí)符
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
// url 為存儲(chǔ)任務(wù)的唯一標(biāo)識(shí)
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
// 如果找到了task,說(shuō)明這個(gè)url已經(jīng)請(qǐng)求過(guò)了,已經(jīng)有存在的任務(wù)了
// 然后根據(jù)傳進(jìn)來(lái)的receiptID創(chuàng)建回調(diào)封裝類
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
// 將回調(diào)封裝類添加到這個(gè)類里面,方面以后多次回調(diào)
[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: {
// 如果策略中包含從緩存中讀取,那么就調(diào)用我們之前的 imageCache 工廠,取出我們已經(jīng)緩存的圖片,就不用每次都請(qǐng)求加載了
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;
// 開(kāi)始異步請(qǐng)求
createdTask = [self.sessionManager
dataTaskWithRequest:request
uploadProgress:nil
downloadProgress:nil
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 有返回結(jié)果了之后,進(jìn)入我們的回調(diào)隊(duì)列
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
// 從我們的緩存中,根據(jù)url取出來(lái)我們保存的已經(jīng)創(chuàng)建好的task封裝類
AFImageDownloaderMergedTask *mergedTask = [strongSelf safelyGetMergedTask:URLIdentifier];
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
// 然后移除
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
if (error) {
// 如果發(fā)生失敗,那么從我們的task封裝類里面,取出回調(diào)封裝類,所有的,然后調(diào)用這個(gè)封裝類里面的失敗的回調(diào),一次回調(diào)多次
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse *)response, error);
});
}
}
} else {
// 成功了之后,選擇是否將圖片緩存,默認(rèn)是允許緩存
if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
// 緩存策略為,key為url拼接AdditionalIdentifier這個(gè)字符串,因?yàn)檫@里傳的nil,所以key就為url,value就為下載的這個(gè)圖片數(shù)據(jù)
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
}
// 取出所有的回調(diào),依次回調(diào)
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.successBlock(request, (NSHTTPURLResponse *)response, responseObject);
});
}
}
}
}
// 將當(dāng)前請(qǐng)求的數(shù)據(jù)-1
[strongSelf safelyDecrementActiveTaskCount];
// 拿出下一個(gè)請(qǐng)求,進(jìn)行請(qǐng)求
[strongSelf safelyStartNextTaskIfNecessary];
});
}];
// 4) Store the response handler for use when the request completes
// 根據(jù)穿件來(lái)的 receiptID 創(chuàng)建封裝回調(diào)類,將成功和失敗回調(diào)封裝進(jìn)去
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
// 創(chuàng)建task封裝類,將url和taskID還有當(dāng)前task傳進(jìn)去保存
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
// 當(dāng)前task保存當(dāng)前封裝的會(huì)叫類
[mergedTask addResponseHandler:handler];
// mergedTasks 緩存中保存當(dāng)前task,注意是以 url 為唯一標(biāo)識(shí)
self.mergedTasks[URLIdentifier] = mergedTask;
// 5) Either start the request or enqueue it depending on the current active request count
// 如果當(dāng)前沒(méi)有達(dá)到最大請(qǐng)求上線,那么就進(jìn)行請(qǐng)求,如果達(dá)到了就入隊(duì)
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;
}
}
這個(gè)方法比較核心,如果發(fā)起來(lái)同一個(gè)請(qǐng)求,會(huì)直接返回已經(jīng)發(fā)起額這個(gè)請(qǐng)求,然后將回調(diào)保存到請(qǐng)求里面,所以就會(huì)出現(xiàn)回調(diào)多次的情況,然后如果我們的緩存策略允許從緩存讀取,也會(huì)優(yōu)先從我們的緩存中讀取,最后返回一個(gè) AFImageDownloadReceipt 對(duì)象,這個(gè)對(duì)象是用來(lái)取消任務(wù)的,
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
// 取出需要取消的tash 的url
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
// 從緩存中找出這個(gè)task
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
// 從這個(gè)task封裝類的responseHandlers里面,這個(gè)responseHandlers里面存放著所有的回調(diào)類,找到和需要需要的這個(gè)receiptID一樣的那個(gè)回調(diào)
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
return handler.uuid == imageDownloadReceipt.receiptID;
}];
if (index != NSNotFound) {
// 找到這個(gè)回調(diào)之后
AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
// 移除這個(gè)回調(diào)
[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];
// 然后拋出錯(cuò)誤
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
// 因?yàn)楫?dāng)前取消的是單個(gè)任務(wù),因?yàn)橛锌赡軙?huì)一個(gè)任務(wù)請(qǐng)求多次但是ID不一樣,如果里面還有別的回調(diào),那么就繼續(xù)下去,如果他的回調(diào)都刪除光了,其實(shí)就是說(shuō)明,當(dāng)前任務(wù)需要取消了,那么就將任務(wù)取消,因?yàn)闆](méi)有接受回調(diào)了
if (mergedTask.responseHandlers.count == 0) {
[mergedTask.task cancel];
// 任務(wù)取消之后,也從緩存中消失
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
下面就是一些同步串行的增刪取操作
- (AFImageDownloaderMergedTask *)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask = nil;
dispatch_sync(self.synchronizationQueue, ^{
mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
});
return mergedTask;
}
//This method should only be called from safely within the synchronizationQueue
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
[self.mergedTasks removeObjectForKey:URLIdentifier];
return mergedTask;
}
- (void)safelyDecrementActiveTaskCount {
dispatch_sync(self.synchronizationQueue, ^{
if (self.activeRequestCount > 0) {
self.activeRequestCount -= 1;
}
});
}
- (void)safelyStartNextTaskIfNecessary {
dispatch_sync(self.synchronizationQueue, ^{
if ([self isActiveRequestCountBelowMaximumLimit]) {
while (self.queuedMergedTasks.count > 0) {
AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[self startMergedTask:mergedTask];
break;
}
}
}
});
}
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
[mergedTask.task resume];
++self.activeRequestCount;
}
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritization) {
case AFImageDownloadPrioritizationFIFO:
[self.queuedMergedTasks addObject:mergedTask];
break;
case AFImageDownloadPrioritizationLIFO:
[self.queuedMergedTasks insertObject:mergedTask atIndex:0];
break;
}
}
- (AFImageDownloaderMergedTask *)dequeueMergedTask {
AFImageDownloaderMergedTask *mergedTask = nil;
mergedTask = [self.queuedMergedTasks firstObject];
[self.queuedMergedTasks removeObject:mergedTask];
return mergedTask;
}
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
- (AFImageDownloaderMergedTask *)safelyGetMergedTask:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask;
// 串行同步讀取
dispatch_sync(self.synchronizationQueue, ^(){
mergedTask = self.mergedTasks[URLIdentifier];
});
return mergedTask;
}