SDWebImage4.0调研

2018-06-27  本文已影响0人  xx明

一、从UIImageView设置imageURL开始读SDWebImage4.0

/**
 * Set the imageView `image` with an `url`.
 *
 * The download is asynchronous and cached.
 *
 * @param url The url for the image.
 */
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    // sd_internalSetImageWithURL为UIView+WebCache中的方法
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                        operationKey:nil
                       setImageBlock:nil
                            progress:progressBlock
                           completed:completedBlock];
}
/*外部通过UIImageView,UIButton等Category setImageWithURL都会调用到UIView+WebCache方法
 * 改方法内部主要做的事情:
 * 1)生成一个validOperationKey,一般通过控件的类名来生成,这个是用来处理对同一个控件同时设置多个imageURL的时候,取消之前的操作的
 * 2)reset progress,将totalUnitCount,completedUnitCount还原为0
 * 3)获取SDWebImageManager单例,执行loadImage操作,并返回一个SDWebImageCombinedOperation
 * 4)将SDWebImageCombinedOperation放在MapTable里面,必要时通过validOperationKey来取消之前的操作
 */
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary<NSString *, id> *)context;

下面是sd_internalSetImageWithURL用到的一些重要方法讲解:

/*loadImage之前会通过validOperationKey先执行一下cancelImageLoadOperation操作
 *注意如果同一个控件同时(很短时间内)设置多个imageURL,operationDictionary才会获取到之前的operation,然后执行cancel操作
 * 一般情况下短时间内某一个控件只设置一次imageURL时operationDictionary 中就不会获取到之前的operation,所以就不会执行cancel操作
 */
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
    id<SDWebImageOperation> operation;
    @synchronized (self) {
        operation = [operationDictionary objectForKey:key];
    }
    if (operation) {
        if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]){
            [operation cancel];
        }
        @synchronized (self) {
            [operationDictionary removeObjectForKey:key];
        }
    }
}
/* 该operation是SDWebImageManager返回的SDWebImageCombinedOperation,它是异步返回的,此时有可能SDWebImageManager内部的imageCache或者imageDownloader还没有开始执行查找缓存或者下载图片。所以当setImageLoadOpetation的时候,就可以通过cancel还取消ioQueue或者downloaderOperation。
 * 然后将SDWebImageCombinedOperation添加到mapTable中
 */
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
    if (key) {
        [self sd_cancelImageLoadOperationWithKey:key];
        if (operation) {
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            @synchronized (self) {
                [operationDictionary setObject:operation forKey:key];
            }
        }
    }
}
id <SDWebImageOperation> operation = [manager loadImageWithURL:url
                                                               options:options
                                                              progress:combinedProgressBlock
                                                             completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
  // loadImage成功
}

下面介绍一下loadImageWithURL:options:progress:completed:方法内部的处理逻辑

/* 每次loadImage的时候都会对应一个CombinedOperation,该对象可以控制图片的cache查找,downLoaderOperation,progressBlock和competedBlock的调用
 * 每个imageURL可能会对应多个CombinedOperation,但是每个imageURL只会对应一个downLoaderOperation
 */
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
[self.failedURLs containsObject:url];
[self.runningOperations addObject:operation];
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key
                                                                  options:cacheOptions
                                                                     done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
  // 这里如果找不到缓存才通过downloader去下载图片
}

下面分析SDWebImageCache如何查找缓存:

/* 如果内存缓存存在的话,通过doneBlock异步将图片回调到上层
 * 但是此时没有创建用于用来操作ioQueue查询的operation
 * queryCacheOperationForKey:options:done返回nil,查询操作完成
 */
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
/* 这里需要创建一个operation
 * 该operation并不是用来执行异步操作的,它可以返回给上层的SDWebImageCombinedOperation,用来做取消操作异步查询操作
 * 异步查询的话,会有一个ioQueue(缓存模块的增删改查都会在这个队列中执行,保证线程安全)串行队列来执行查询操作
 */
NSOperation *operation = [NSOperation new];
    void(^queryDiskBlock)(void) =  ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        
        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            SDImageCacheType cacheType = SDImageCacheTypeDisk;
            if (image) {
                // the image is from in-memory cache
                diskImage = image;
                cacheType = SDImageCacheTypeMemory;
            } else if (diskData) {
                // decode image data only if in-memory cache missed
                diskImage = [self diskImageForKey:key data:diskData options:options];
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = SDCacheCostForImage(diskImage);
                    [self.memCache setObject:diskImage forKey:key cost:cost];
                }
            }
            
            if (doneBlock) {
                if (options & SDImageCacheQueryDiskSync) {
                    doneBlock(diskImage, diskData, cacheType);
                } else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, cacheType);
                    });
                }
            }
        }
    };
- (void)cancel {
    @synchronized(self) {
        self.cancelled = YES;
        if (self.cacheOperation) {
            // 将没有查询cache的operation置为cancel状态
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        if (self.downloadToken) {
            // 将还没有执行的progressBlock,completedBlock移除,不再回调
            // 取消downloaderOperation的下载操作
            [self.manager.imageDownloader cancel:self.downloadToken];
        }
        [self.manager safelyRemoveOperationFromRunning:self];
    }
}
/* imageDownloader会返回一个downloadToken,让SDWebImageCombinedOperation持有。
 * downloadToken准守SDWebImageOperation协议,有两个属性url和downloadOperationCancelToken(这个其实是一个保存了progressBlock和completedBlock的字典)
 * 因为SDWebImageCombinedOperation与downloaderOperation是多对一的关系(即一个url只有一个downloaderOperation,但是每次loadImage的时候都会对应一个SDWebImageCombinedOperation),
 * downloaderOperation需要有一个数组存储外部传入的progressBlock和completedBlock。
 * 而这个token正是存储这两个block的字典,放在一个callbackBlocks数组里
 * 
 */
strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url
                                                                               options:downloaderOptions
                                                                              progress:progressBlock
                                                                             completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
  // 下载完成
}

下面解释一下downloadImageWithURL:options:progress:completed内部的处理

// 默认下载最大并发量为6
[self addProgressCallback:progressBlock
                      completedBlock:completedBlock
                              forURL:url
                      createCallback:^SDWebImageDownloaderOperation *{
  // createDownloaderOperation完成之后,执行下载操作
  // 设置优先级
}
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished) {
        operation = createCallback();
        __weak typeof(self) wself = self;
        operation.completionBlock = ^{
            __strong typeof(wself) sself = wself;
            if (!sself) {
                return;
            }
            LOCK(sself.operationsLock);
            [sself.URLOperations removeObjectForKey:url];
            UNLOCK(sself.operationsLock);
        };
        [self.URLOperations setObject:operation forKey:url];
        // Add operation to operation queue only after all configuration done according to Apple's doc.
        // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
        [self.downloadQueue addOperation:operation];
    }
// 改存储操作保证了多回调的实现,即不同控件设置同一个url,保证每个控件都能显示出改图片,而且downloadOperation只执行一次
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];

二、SDWebImage相关的queue和operation

_downloadQueue.maxConcurrentOperationCount = 6;
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
上一篇 下一篇

猜你喜欢

热点阅读