美文网首页
SDWebImage源码初探(一)

SDWebImage源码初探(一)

作者: Invoker_M | 来源:发表于2017-07-19 09:45 被阅读0次

    最近项目进度缓慢了下来,决定看看各种源码来涨点知识。就先从SDWebImage开始吧!

    在项目中用的最多的方法应该是UIImageView+WebCache与UIButton+WebCache里面的sd_setImageWithURL:这个系列的方法了。这里从UIImageView+WebCache开始看起。

    其中该系列所有方法都基于:

    
    - (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL*)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
    
    

    参数很简单明了,url是需要加载的图片url;placeholder是在url指向的图片加载完成之前暂时使用的图片;options是个枚举,用来控制加载图片时的一些设置(具体百度即可,大把);progressBlock则可以通过返回的receivedSize与expectedSize来花式展现下载进度;completedBlock则是下载完成后的Block;

    下面来看作者的具体实现:

    
    [self sd_cancelCurrentImageLoad];
    
    

    按照意思来看是取消当前所有的图片加载,进入方法内部看:

    
    - (void)sd_cancelImageLoadOperationWithKey:(NSString*)key {
    
    // Cancel in progress downloader from queue
    
    NSMutableDictionary*operationDictionary = [selfoperationDictionary];
    
    idoperations = [operationDictionaryobjectForKey:key];
    
    if(operations) {
    
    if([operationsisKindOfClass:[NSArrayclass]]) {
    
    for(id operationinoperations) {
    
    if(operation) {
    
    [operationcancel];
    
    }
    
    }
    
    }elseif([operationsconformsToProtocol:@protocol(SDWebImageOperation)]){
    
    [(id) operationscancel];
    
    }
    
    [operationDictionaryremoveObjectForKey:key];
    
    }
    
    }
    
    

    首先取出一个operationDictionary,在取出这个字典的过程中,作者在分类中使用了objc_setAssociatedObject与objc_getAssociatedObject,来给分类添加属性。

    接着通过传过来的key(@"UIImageViewImageLoad")来获取ImageView的加载队列。

    最后,花式cancle掉这个队列中的任务。

    
    [operation cancel];
    
    [operation DictionaryremoveObjectForKey:key];
    
    

    回到主方法,第二行又通过objc_setAssociatedObject方法将url关联到分类中,接着通过位与运算判断下option是否为SDWebImageDelayPlaceholder,来设置默认的占位图片。其中dispatch_main_async_safe这个宏很好用,避免了在主线程中造成死锁的情况。

    然后是判断一下是否需要转动菊花:

    
    if([selfshowActivityIndicatorView]) {
    
    [selfaddActivityIndicator];
    
    }
    
    

    接下来是这个方法的核心部分,调用了SDWebImageManager中的

    
    - (id)downloadImageWithURL:(NSURL*)url
    
    options:(SDWebImageOptions)options
    
    progress:(SDWebImageDownloaderProgressBlock)progressBlock
    
    completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
    
    

    由此方法完成图片的下载。

    我们可以跳到SDWebImageManager.h中查看一下作者对于该方法的描述:

    翻译过来的意思大概是:如果URL指定的图片不在缓存中就下载该图片,否则就返回缓存的版本。

    回到这个方法的实现,前几行是判断URL的类型是否正确。

    
    BOOLisFailedUrl =NO;
    
    @synchronized(self.failedURLs) {
    
    isFailedUrl = [self.failedURLscontainsObject:url];
    
    }
    
    

    这几句来获取传入的URL是否为之前下载失败过的URL,用一个BOOL值来记录下来。

    如果URL不为空或者未设置options为SDWebImageRetryFailed项、且URL在黑名单之中,就会直接返回掉。

    
    @synchronized(self.runningOperations) {
    
    [self.runningOperationsaddObject:operation];
    
    }
    
    

    这段代码是先给运行中的下载队列加锁,避免多个线程同时对数组进行操作,将一个SDWebImageCombinedOperation对象加入到下载队列中。

    
    NSString*key = [selfcacheKeyForURL:url];
    
    operation.cacheOperation= [self.imageCache queryDiskCacheForKey:key done:^(UIImage*image,SDImageCacheTypecacheType) {
    
    if(operation.isCancelled) {
    
    @synchronized(self.runningOperations) {
    
    [self.runningOperationsremoveObject:operation];
    
    }
    
    return;
    
    }
    
    

    将图片的URL当做key值,再调用 queryDiskCacheForKey:done:来获取缓存中的图片。
    我们来看看这个方法的内部实现:

    - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
        if (!doneBlock) {
            return nil;
        }
    
        if (!key) {
            doneBlock(nil, SDImageCacheTypeNone);
            return nil;
        }
    
        // First check the in-memory cache...
        UIImage *image = [self imageFromMemoryCacheForKey:key];
    //这里封装了NSChace的objectForKey方法,直接从内存缓存中获取图片对象
        if (image) {
            doneBlock(image, SDImageCacheTypeMemory);
            return nil;
        }
    //如果内存缓存中获取到则直接返回
    
        NSOperation *operation = [NSOperation new];
        dispatch_async(self.ioQueue, ^{
            if (operation.isCancelled) {
                return;
            }
    //创建一个串行队列来获取磁盘缓存中的图片
            @autoreleasepool {
    //创建内存池来及时的释放内存
                UIImage *diskImage = [self diskImageForKey:key];
    //获取磁盘缓存中的图片
                if (diskImage && self.shouldCacheImagesInMemory) {
    //如果磁盘缓存中有该图片,并且设置将图片缓存到内存中,则取出磁盘缓存的图片并且将其放入内存缓存中
                    NSUInteger cost = SDCacheCostForImage(diskImage);
    //计算出图片需要开销的内存大小
                    [self.memCache setObject:diskImage forKey:key cost:cost];
    //将图片缓存到内存中
                }
    
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, SDImageCacheTypeDisk);
    //在主线程中回调Block
                });
            }
        });
    
        return operation;
    }
    
    

    缓存这里获取完成之后,来看下面的代码,有点长,我们分解开来一部分一部分阅读,先看这个判断:

    if ((!image || options & SDWebImageRefreshCached)
    //图片未从缓存中获取,或者是 option设置需要刷新缓存
    && (![self.delegate respondsToSelector:
    @selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]))
    /*这部分条件中,如果imageManager:shouldDownloadImageForURL:方法未实现、或是实现了并且返回YES。可以从方法的名字中来理解,代理方法返回的BOOL为是否应该下载URL对应的图片。
    */
    

    总而言之就是判断各种条件之下,图片是否应该被下载,让我们进入方法内部。

    if (image && options & SDWebImageRefreshCached) {
                    dispatch_main_sync_safe(^{
                        // If image was found in the cache bug SDWebImageRefreshCached is provided, notify about the cached image
                        // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
    //翻译一下,如果图片在缓存中被找到但是options设置了SDWebImageRefreshCached(刷新缓存),通知这个缓存图片,并且试图从新下载这个图片,让服务端有机会刷新这个缓存。
                        completedBlock(image, nil, cacheType, YES, url);
                    });
                }
    
    SDWebImageDownloaderOptions downloaderOptions = 0;
    //初始化downloaderOptions
                if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
                //如果options为低优先级,则设置downloaderOptions 为SDWebImageDownloaderLowPriority,下面的同理。
                if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
                if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
                if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
                if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
                if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
                if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
                if (image && options & SDWebImageRefreshCached) {
                    // force progressive off if image already cached but forced refreshing
                    downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                    // ignore image read from NSURLCache if image if cached but force refreshing
                    downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
                    //翻译一哈,两行代码的意思是,如果图片存在缓存并且需要刷新缓存,则强制取消掉SDWebImageDownloaderProgressiveDownload模式(渐进下载),
    然后忽略从缓存中读取的图片。
                }
    
    

    接下来使用SDWebImageDownloader来执行一个下载任务

    id <SDWebImageOperation> subOperation = 
    [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:
    ^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) 
    

    来看下载completedBlock中的第一部分处理

    if (weakOperation.isCancelled) {
                      /*这里什么都没做操作,源代码中提及了#699号更新,于是我去看了下,大概意思是说:
    当weakOperation取消的时候不要试图去调用completion block,dispatch_main_sync_safe()也无法保证这个block被终止的时候没有其他的代码在运行,所以其他代码运行时可能会被截断。
    比如说,如果取消weakOperation后再调用completion block,那么在随后的一个TableViewCell中加载Image的completion block将会和这个completion block产生竞争关系。说的通俗一点就是,先调用的completion block里面的数据可能会被第二个completion block的数据覆盖掉。
    */
                    }
                    else if (error) {
                        dispatch_main_sync_safe(^{
                            if (!weakOperation.isCancelled) {
                                completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                            }
    //有错误信息,完成回调。
                        });
    
                        if (error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut) {
                            @synchronized (self.failedURLs) {
                                [self.failedURLs addObject:url];
    //将发送错误的URL添加到黑名单里面
                            }
                        }
                    }
    
    else {
                        BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
    
                        if (options & SDWebImageRefreshCached && image && !downloadedImage) {
    //options设置为SDWebImageRefreshCached选项,在缓存中又找到了image且没有下载成功
                            // Image refresh hit the NSURLCache cache, do not call the completion block
                          //图片刷新时遇到了具有缓存的情况,不调用 completion block
                        }
                        else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage))
                        && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        //图片下载成功且图片设置为SDWebImageTransformAnimatedImage并且实现了imageManager:transformDownloadedImage:withURL:方法            
              dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                                UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                                //调用delegate方法完成图片的变形
                                if (transformedImage && finished) {
                                    BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                    [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:data forKey:key toDisk:cacheOnDisk];
                                  //将变形后的图片缓存起来
                                }
    
                                dispatch_main_sync_safe(^{
                                    if (!weakOperation.isCancelled) {
                                        completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                  //在主线程中回调completedBlock
                                    }
                                });
                            });
                        }
                        else {
                            if (downloadedImage && finished) {
                                [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                              //如果没设置图片变形,并且下载完成,则直接缓存图片
                            }
    
                            dispatch_main_sync_safe(^{
                                if (!weakOperation.isCancelled) {
                                    completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                                //在主线程中完成回调
                                }
                            });
                        }
                    }
    if (finished) {
                        @synchronized (self.runningOperations) {
                            [self.runningOperations removeObject:operation];
                       //从下载队列移除
                        }
                    }
                }];
    
    operation.cancelBlock = ^{
                    [subOperation cancel];
                    @synchronized (self.runningOperations) {
                        [self.runningOperations removeObject:weakOperation];
                    
                    }
                };
                 //设置operation取消之后的一些操作
    
    else if (image) {
            else if (image) {
                dispatch_main_sync_safe(^{
                    if (!weakOperation.isCancelled) {
                        completedBlock(image, nil, cacheType, YES, url);
                    }
                    //在缓存中找到图片并且设置了不能下载的选项,完成回调
                });
                @synchronized (self.runningOperations) {
                    [self.runningOperations removeObject:operation];
                }
            }
            else {
             //在缓存中没有找到图片,并且设置不能下载的选项
          
                dispatch_main_sync_safe(^{
                    if (!weakOperation.isCancelled) {
                        completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                    }
                 //完成回调
                });
                @synchronized (self.runningOperations) {
                    [self.runningOperations removeObject:operation];
                }
            }
        }];
    
        return operation;
    

    这么一大段的方法按照功能排序来看,分解为首先创建下载operation,再读取系统的内存缓存与磁盘缓存,接着判断是否需要下载来进行下载操作,最后对下载的图片进行处理。

    相关文章

      网友评论

          本文标题:SDWebImage源码初探(一)

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