网络请求

iOS AFN AFImageDownloader 源码分析

2020-06-28  本文已影响0人  孙掌门

AFN AFImageDownloader 源码分析

源码也是很简单,再次来学人家的思想,其实读源码最终就是学习人家的设计和思想,到最后你会变成一个特别喜欢读源码的人

AFImageDownloaderResponseHandler

我们正常在设计接口的时候,都会留给别人一个成功或者失败的回调,但有没有想过一个问题,比如我一个操作调用多次,但这个操作是重复的,但我只关心成功或者失败的回调,那么需要怎么办?或者换句话说,我们平时调用代理的时候,设置代理,都是设置单一代理,那么这个代理,最终只会被回到一次到一个地方,我们也是会有需求,设置多个代理,那么怎么做呢?

这里作者将一个接口中的成功或者失败额回调,抽象到一个具体的类里面去保存,而之后将这个类保存到一个字典中,每次的key不同,就能实现,多次回调block或者代理的需求了

AFImageDownloaderMergedTask

这个类就是对下载图片task的一个封装

@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

可以看到他保存着请求的url,id标识,任务task,和上面所说的回调数组,也就是说一个task可以有多个response回调block

AFImageDownloader



+ (NSURLCache *)defaultURLCache {
    // 这篇文章不错: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;
}

这里会造一个NSURLCache的类,用于缓存,主要是配置个系统的NSURLSessionConfiguration这个类,是依据这个属性的requestCachePolicy,也就是说缓存策略,是否从缓存中读取,如果这个策略中选择了从缓存中读取,那么我们下载完图片之后会存放在缓存中,下次同一个请求,就可以不请求了,直接从缓存中读取图片就可以了,后面会讲到,,这里可以看到,申请内存缓存20M,磁盘缓存250M,

- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
                downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
                maximumActiveDownloads:(NSInteger)maximumActiveDownloads
                            imageCache:(id <AFImageRequestCache>)imageCache {
    if (self = [super init]) {
        self.sessionManager = sessionManager;

        // 下载策略,先进先出,还是先进后出
        self.downloadPrioritization = downloadPrioritization;
        // 允许最大下载个数
        self.maximumActiveDownloads = maximumActiveDownloads;
        // 图片缓存实现,AFN提供了缓存策略,也可以自己实现
        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;
}

- (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, ^{
        // url 为唯一标识符
        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 为存储任务的唯一标识
        AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
        if (existingMergedTask != nil) {
            // 如果找到了task,说明这个url已经请求过了,已经有存在的任务了
            // 然后根据传进来的receiptID创建回调封装类
            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: {
                // 如果策略中包含从缓存中读取,那么就调用我们之前的 imageCache 工厂,取出我们已经缓存的图片,就不用每次都请求加载了
                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;
                               // 从我们的缓存中,根据url取出来我们保存的已经创建好的task封装类
                               AFImageDownloaderMergedTask *mergedTask = [strongSelf safelyGetMergedTask:URLIdentifier];
                               if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
                                   // 然后移除
                                   mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
                                   if (error) {
                                       // 如果发生失败,那么从我们的task封装类里面,取出回调封装类,所有的,然后调用这个封装类里面的失败的回调,一次回调多次
                                       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]) {
                                           // 缓存策略为,key为url拼接AdditionalIdentifier这个字符串,因为这里传的nil,所以key就为url,value就为下载的这个图片数据
                                           [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);
                                               });
                                           }
                                       }
                                       
                                   }
                               }
                               // 将当前请求的数据-1
                               [strongSelf safelyDecrementActiveTaskCount];
                               // 拿出下一个请求,进行请求
                               [strongSelf safelyStartNextTaskIfNecessary];
                           });
                       }];

        // 4) Store the response handler for use when the request completes
        // 根据穿件来的 receiptID 创建封装回调类,将成功和失败回调封装进去
        AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                                   success:success
                                                                                                   failure:failure];
        // 创建task封装类,将url和taskID还有当前task传进去保存
        AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                                   initWithURLIdentifier:URLIdentifier
                                                   identifier:mergedTaskIdentifier
                                                   task:createdTask];
        // 当前task保存当前封装的会叫类
        [mergedTask addResponseHandler:handler];
        // mergedTasks 缓存中保存当前task,注意是以 url 为唯一标识
        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 对象,这个对象是用来取消任务的,


- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    dispatch_sync(self.synchronizationQueue, ^{
        // 取出需要取消的tash 的url
        NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
        // 从缓存中找出这个task
        AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
        // 从这个task封装类的responseHandlers里面,这个responseHandlers里面存放着所有的回调类,找到和需要需要的这个receiptID一样的那个回调
        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);
                });
            }
        }
        // 因为当前取消的是单个任务,因为有可能会一个任务请求多次但是ID不一样,如果里面还有别的回调,那么就继续下去,如果他的回调都删除光了,其实就是说明,当前任务需要取消了,那么就将任务取消,因为没有接受回调了
        if (mergedTask.responseHandlers.count == 0) {
            [mergedTask.task cancel];
             // 任务取消之后,也从缓存中消失
            [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;
}


上一篇下一篇

猜你喜欢

热点阅读