SDWebImage源码初探(一)

最近项目进度缓慢了下来,决定看看各种源码来涨点知识。就先从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  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源码初探(一))