美文网首页
SDWebImage4.0调研

SDWebImage4.0调研

作者: xx明 | 来源:发表于2018-06-27 14:10 被阅读0次

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

  • 1、设置url
/**
 * 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;
  • 2、最终会都会在UIView+WebCache中统一处理
- (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];
}
  • 3、UIView+WebCache核心方法
/*外部通过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用到的一些重要方法讲解:

  • 3.1、sd_cancelImageLoadOperationWithKey:sd_setImageLoadOperation:forKey:
/*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];
            }
        }
    }
}
  • 3.2、获取SDWebImageManager的实例manger
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:方法内部的处理逻辑

  • 3.2.1 创建SDWebImageCombinedOperation
/* 每次loadImage的时候都会对应一个CombinedOperation,该对象可以控制图片的cache查找,downLoaderOperation,progressBlock和competedBlock的调用
 * 每个imageURL可能会对应多个CombinedOperation,但是每个imageURL只会对应一个downLoaderOperation
 */
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
  • 3.2.1 判断url是否在failedURLs,下载失败的imageURLs不会再走第二遍,但是此时CombinedOperation已经创建了
[self.failedURLs containsObject:url];
  • 3.2.2 将SDWebImageCombinedOperation添加到runningOperations数组里
[self.runningOperations addObject:operation];
  • 3.2.3 调用SDImageCache的queryCacheOperationForKey:options:done方法去查询缓存。同时SDWebImageCombinedOperationcacheOperation持有SDImageCache查询缓存的operation
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key
                                                                  options:cacheOptions
                                                                     done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
  // 这里如果找不到缓存才通过downloader去下载图片
}

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

  • 3.2.3.1 首先查询内存,此时不需要创建operation。
/* 如果内存缓存存在的话,通过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;
    }
  • 3.2.3.2 如果内存中没有查到,去查询磁盘缓存
/* 这里需要创建一个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);
                    });
                }
            }
        }
    };
  • 3.2.4、这里先讲解一下SDWebImageCombinedOperation的cancel操作
- (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];
    }
}
  • 3.2.5、如果缓存没有命中,就会调用SDWebImageDownloader去下载图片 downloadImageWithURL:options:progress:completed
/* 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内部的处理

  • 3.2.5.1该方法内部调用了
// 默认下载最大并发量为6
[self addProgressCallback:progressBlock
                      completedBlock:completedBlock
                              forURL:url
                      createCallback:^SDWebImageDownloaderOperation *{
  // createDownloaderOperation完成之后,执行下载操作
  // 设置优先级
}
  • 3.2.5 下载核心方法addProgressCallback:completedBlock:forURL:createCallback探究
  • 3.2.6.1 根据url获取下载队列
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
  • 3.2.6.2 如果下载队列不存在,或者下载队列已经完成的话,去创建下载队列,执行下载,并将改operation添加到全局队列中,这里就避免了不同的控件去加载同一个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];
    }
  • 3.2.6.3存储progressBlockcompletedBlock,并生成一个downloadOperationCancelToken
// 改存储操作保证了多回调的实现,即不同控件设置同一个url,保证每个控件都能显示出改图片,而且downloadOperation只执行一次
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
  • 3.3请求超时时间默认为15s
  • 3.4 sd为了避免“双Cache”即(NSURLCache + SDWebImageCache)的情况,默认会不使用NSURLCache,除非指定
  • 4、SDWebImageCombinedOperation非常重要,它虽然定义为operation,但是其实并不是operation。
  • 4.1SDWebImageCombinedOperation持有了SDWebImageManger的弱引用
  • 4.2 SDWebImageCombinedOperation持有了SDImageCache的operation
  • 4.3)SDWebImageCombinedOperation持有了SDWebImageDownloadToken
  • 4.4 cancel方法,可以取消cacheOperation,取消imageDownloader, 从SDWebImageManager的runningOperation中移除它自己

二、SDWebImage相关的queue和operation

  • 1、SDWebImageDownloader
  • 1.1 NSOperationQueue *downloadQueue 默认最大并发数为6 用来管理SDWebImageDownloadOperation
_downloadQueue.maxConcurrentOperationCount = 6;
  • 1.2 SDWebImageDownloadOperation 包含一个request,一个dataTask,一个response,
  • 1.3 cancel:方法(这个方法用来删除指定的callbackBlocks元素,通过removeObjectIdenticalTo:按对象地址删除),如果callbackBlocks中没有元素了,就调用cancel方法,并且停止 cancel dataTask任务
  • 1.4 downloaderOperation中会创建一个图片decoder队列,用来处理NSData
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
  • 2、SDImageCache
  • 2.1 dispatch_queue_t ioQueue 串行队列,来处理所有的图片相关的io操作,保证多线程线程安全
  • 2.2 如果只查询内存缓存的,不会返回operation,如果在磁盘上查询则给上层(SDWebImageManager的SDWebImageCombinedOperation)返回一个operation

相关文章

  • SDWebImage4.0调研

    一、从UIImageView设置imageURL开始读SDWebImage4.0 1、设置url 2、最终会都会在...

  • SDWebImage 4.0源码学习

    SDWebImage源码学习基于版本4.0 源码注释: SDWebImage4.0 以前看见别人的轮子感觉太高深,...

  • iOS SDWebImage 4.0 源码探究

    推荐阅读 SDWebImage4.0源码探究(一)面试题https://www.jianshu.com/p/b85...

  • SDWebImage4.0源码阅读笔记(二)

    紧接着上一篇文章,在这篇文章里面,我会先从 SDWebImageManager 中的 loadImageWithU...

  • 关于调研

    按照调研是否为调研者独立完成,我把调研分为两类:独立调研和非独立调研。 独立调研,即调研者自己寻找相关信息,看自己...

  • 产品入门part6 - 调研报告

    01 调研的类别 调研分为功能点调研和产品调研 功能调研:重业务流程、交互体验、突出亮点。 产品调研:重产品结构、...

  • 从用户到产品,一款贴近用户的互联网产品设计必须经历的6个阶段(一

    为什么要做市场调研,很简单,一句话,为了得到用户画像。市场调研包括了行业调研、广告调研、竞争情报调研、品牌调研、消...

  • 产品调研方法

    要清晰知道调研目的,调研对象,需要收集的数据,需要调研达到什么效果。 调研方法可以: 1.文案调研 主要是在网上资...

  • 独立产品的调研

    如何做独立产品的调研 1.功能调研和产品调研的区别? 2.调研报告的误区 产品调研和功能调研出发点都是一样的,都注...

  • Java项目开发流程

    1.项目启动阶段: *项目描述、*项目目标 *项目实施: -项目调研(业务调研,技术调研,编写项目调研报告) -工...

网友评论

      本文标题:SDWebImage4.0调研

      本文链接:https://www.haomeiwen.com/subject/egebeftx.html