SDWebImage原理探索

话说想要提高必须站在巨人的肩膀上,最近没事就研究一下SDWebImage的源码

SDWebImage 是我们经常使用的一个异步图片加载库,在项目中使用SDWebImage来管理图片加载相关操作可以极大地提高开发效率,让我们更加专注于业务逻辑实现。

SDWebImage的实现流程
SDWebimage.png
SDWebimage目录结构
目录结构.png
实现原理

SDWebImage 是由一个 SDImageCache(一个处理缓存的类) 和 SDWebImageDownloader(负责下载网络图片) ,而 SDWebImageManager则是管理者将前两者结合起来完成整个工作流程。

1 、入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage显示,然后SDWebImageManager 根据 URL 开始处理图片。

2、进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

3、先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate回调 imageCache:didFindImage:forKey:userInfo 到 SDWebImageManager。

4、SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。

5、如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。

6、根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:

7、如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

8、如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:

9、共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

10、connection:didReceiveData: 中利用ImageIO 做了按图片下载进度加载效果。

connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

11、图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader

imageDownloader:didFinishWithImage:回调给 SDWebImageManager告知图片下载完成。

通知所有的downloadDelegates下载完成,回调给需要的地方展示图片。

12、将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独NSInvocationOperation完成,避免拖慢主线程。

SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。

SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。

SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

SDWebImageManager管理类

SDWebImageManager管理类中一共有12个枚举方法,3个回调的block,2个代理方法,8个成员方法
12个枚举方法

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    
    /**
     * 默认情况下,如果一个url在下载的时候失败了,那么这个url会被加入黑名单,不会尝试再次下载。如果使用该参数,则该URL不会被添加到黑名单中。意味着会对下载失败的URL尝试重新下载。
     * 此标记取消黑名单
     */
    SDWebImageRetryFailed = 1 << 0, //失败后尝试重新下载
    
    /**
     * 默认情况下,在 UI 交互时也会启动图像下载,此标记取消这一特性
     * 会推迟到滚动视图停止滚动之后再继续下载
     * 备注:NSURLConnection 的网络下载事件监听的运行循环模式是 NSDefaultRunLoopMode
     */
    SDWebImageLowPriority = 1 << 1, //低优先级
   
    SDWebImageCacheMemoryOnly = 1 << 2, //只使用内存缓存

    SDWebImageProgressiveDownload = 1 << 3, //渐进式下载
    
    /**
     * 遵守 HTPP 响应的缓存控制,如果需要,从远程刷新图像
     * 磁盘缓存将由 NSURLCache 处理,而不是 SDWebImage,这会对性能有轻微的影响
     * 此选项用于处理URL指向图片发生变化的情况
     * 如果缓存的图像被刷新,会调用一次 completion block,并传递最终的图像
     */
    SDWebImageRefreshCached = 1 << 4,   //刷新缓存
    

    SDWebImageContinueInBackground = 1 << 5,    //后台下载
    
    SDWebImageHandleCookies = 1 << 6,   //处理保存在NSHTTPCookieStore中的cookies
    
 
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,     //允许不信任的 SSL 证书
    
    /**
     *  默认情况下,图像会按照添加到队列中的顺序被加载,此标记会将它们移动到队列前端被立即加载
     *  而不是等待当前队列被加载,因为等待队列加载会需要一段时间
     */
    SDWebImageHighPriority = 1 << 8,    //高优先级(优先下载)
    
    /**
     * 默认情况下,在加载图像时,占位图像已经会被加载。
     * 此标记会延迟加载占位图像,直到图像已经完成加载
     */
    SDWebImageDelayPlaceholder = 1 << 9,    //延迟占位图片
    
    SDWebImageTransformAnimatedImage = 1 << 10, //转换动画图像
    
    SDWebImageAvoidAutoSetImage = 1 << 11   //手动设置图像
};

3个block回调

//定义任务完成的block块
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
//定义任务结束的block块
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
//定义缓存过滤器的block块
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

2个代理

/**
 * 如果该图片没有缓存,那么下载
 *
 * @param imageManager:当前的SDWebImageManager
 * @param imageURL:要下载图片的URL地址
 *
 * @return 如果要下载的图片在缓存中不存在,则返回NO,否则返回YES
 */
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/**

 * 允许在下载后立即将图像转换,并进行磁盘和内存缓存。
 *
 * @param imageManager 当前的SDWebImageManager
 * @param image 要转换你的图片
 * @param imageURL 要转换的图片的URL地址
 *
 * @return 变换后的图片对象
 */
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;

成员方法
该方法是管理类中最重要的方法

 /* 如果URL对应的图像在缓存中不存在,那么就下载指定的图片 ,否则返回缓存的图像
 *
 * @param url 图片的URL地址
 * @param options 指定此次请求策略的选项
 * @param progressBlock 图片下载进度的回调
 * @param completedBlock 操作完成后的回调
 *      此参数是必须的,此block没有返回值
 *      Image:请求的 UIImage,如果出现错误,image参数是nil
 *      error:如果出现错误,则error有值
 *      cacheType:`SDImageCacheType` 枚举,标示该图像的加载方式
 *          SDImageCacheTypeNone:从网络下载
 *          SDImageCacheTypeDisk:从本地缓存加载
 *          SDImageCacheTypeMemory:从内存缓存加载
 *          finished:如果图像下载完成则为YES,如果使用 SDWebImageProgressiveDownload 选项,同时只获取到部分图片时,返回 NO
 *          imageURL:图片的URL地址
 *
 * @return SDWebImageOperation对象,应该是SDWebimageDownloaderOperation实例
 */
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBloc
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

方法解释

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // 没有completedblock,那么调用这个方法是毫无意义的
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

     //检查用户传入的URL是否正确,如果该URL是NSString类型的,那么尝试转换
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    //防止因参数类型错误而导致应用程序崩溃,判断URL是否是NSURL类型的,如果不是则直接设置为nil
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    //初始化一个SDWebImageCombinedOperationBlock块
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    //如果url不正确或者 选择的下载策略不是『下载失败尝试重新下载』且该URL存在于黑名单中,那么直接返回,回调任务完成block块,传递错误信息
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }

    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    
    //得到该URL对应的缓存KEY
    NSString *key = [self cacheKeyForURL:url];
    //该方法查找URLKEY对应的图片缓存是否存在,查找完毕之后把该图片(存在|不存在)和该图片的缓存方法以block的方式传递
    //缓存情况查找完毕之后,在block块中进行后续处理(如果该图片没有缓存·下载|如果缓存存在|如果用户设置了下载的缓存策略是刷新缓存如何处理等等)
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }

        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            if (cachedImage && options & SDWebImageRefreshCached) {
               
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

        
            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options & SDWebImageLowPriority) 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 (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
            
            if (cachedImage && 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;
            }
            //核心方法:使用下载器,下载图片
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {
                    //如果是取消操作,就什么也不做
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                    //如果下载失败,则处理结束的回调,在合适的情况下把对应图片的URL添加到黑名单中
                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost
                        && error.code != NSURLErrorNetworkConnectionLost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //是否要进行磁盘缓存?
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        //否则,如果下载图片存在且(不是可动画图片数组||下载策略为SDWebImageTransformAnimatedImage&&transformDownloadedImage方法可用)
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            
                             //在下载后立即将图像转换,并进行磁盘和内存缓存
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            //在主线程中回调completedBlock
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        if (downloadedImage && finished) {
                            //得到下载的图片且已经完成,则进行缓存处理
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }

                if (finished) {
                    // 下载结束,移除队列
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            //取消操作
            operation.cancelBlock = ^{
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
        } else if (cachedImage) {
            //缓存
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        } else {
            
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;
}

其他方法

/**
 * 根据图片的URL保存图片到缓存
 *
 * @param image:缓存的图片
 * @param url:该图片的URL地址
 */
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
/**
 * 取消当前所有操作
 */
- (void)cancelAll;
/**
 * 检查一个或多个操作是否正在运行
 */
- (BOOL)isRunning;
/*
 *  检查图像是否已经被缓存,如果已经缓存则返回YES
 *  @param url:图片对应的URL
 *
 *  @return 返回是否存在的BOOL值
 */
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
/**
 *  检查图像是否存在磁盘缓存(此方法仅针对磁盘进行检查,只要存在就返回YES)
 *
 *  @param url 图片的url
 *
 *  @return 是否存在(只检查磁盘缓存)
 */
- (BOOL)diskImageExistsForURL:(NSURL *)url;
/**
 *  异步检查图像是否已经有内存缓存
 *
 *  @param URL             图片对应的URL
 *  @param completionBlock 当任务执行完毕之后调用的block
 *  @note  completionBlock始终在主队列执行
 */
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
 *  异步检查图像是否已经有磁盘缓存
 *
 *  @param URL             图片对应的URL
 *  @param completionBlock 当任务执行完毕之后调用的block
 *  @note  completionBlock始终在主队列执行
 */
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
 * 返回指定URL的缓存键值,就是URL字符串
 */
- (NSString *)cacheKeyForURL:(NSURL *)url;

SDWebImageDownloader下载类

SDWebImageDownloader的头文件内容比较少,主要是定义了一些基本参数如下载优先级策略、最大并发数、超时时间等

SDWebImageDownloader 中核心方法:下载图片的操作
/*
 * 使用给定的 URL 创建 SDWebImageDownloader 异步下载器实例
 * 图像下载完成或者出现错误时会通知代理
 */
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                            options:(SDWebImageDownloaderOptions)options
                       progress:(SDWebImageDownloaderProgressBlock)progressBlock                                                                                    
                    completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;  //为了避免block的循环引用
    //处理进度回调|完成回调等,如果该url在self.URLCallbacks并不存在,则调用createCallback block块
    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
        
        //处理下载超时,如果没有设置过则初始化为15秒
        NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }
        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
        //根据给定的URL和缓存策略创建可变的请求对象,设置请求超时
        //请求策略:如果是SDWebImageDownloaderUseNSURLCache则使用NSURLRequestUseProtocolCachePolicy,否则使用NSURLRequestReloadIgnoringLocalCacheData
        /*
         NSURLRequestUseProtocolCachePolicy:默认的缓存策略
            1)如果缓存不存在,直接从服务端获取。
            2)如果缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端.
         NSURLRequestReloadIgnoringLocalCacheData:忽略本地缓存数据,直接请求服务端。
         */
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        
        //设置是否使用Cookies(采用按位与)
        /*
         关于cookies参考:http://blog.csdn.net/chun799/article/details/17206907
         */
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        //开启HTTP管道,这可以显著降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的
        request.HTTPShouldUsePipelining = YES;
        
        //设置请求头信息(过滤等)
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }
        
        //核心方法:创建下载图片的操作
        operation = [[wself.operationClass alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            SDWebImageDownloader *sself = wself;
            if (!sself) return;
            __block NSArray *callbacksForURL;
            dispatch_sync(sself.barrierQueue, ^{
                callbacksForURL = [sself.URLCallbacks[url] copy];
            });
            
            //遍历callbacksForURL数组中的所有字典,执行SDWebImageDownloaderProgressBlock回调
            for (NSDictionary *callbacks in callbacksForURL) {
                //说明:SDWebImageDownloaderProgressBlock作者可能考虑到用户拿到进度数据后会进行刷新处理,因此在主线程中处理了回调
                dispatch_async(dispatch_get_main_queue(), ^{
                    SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                    if (callback) callback(receivedSize, expectedSize);
                });
            }
        } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
            SDWebImageDownloader *sself = wself;
            if (!sself) return;
            __block NSArray *callbacksForURL;
            
            dispatch_barrier_sync(sself.barrierQueue, ^{
                callbacksForURL = [sself.URLCallbacks[url] copy];
                
                //如果完成,那么把URL从URLCallbacks字典中删除
                if (finished) {
                    [sself.URLCallbacks removeObjectForKey:url];
                }
            });
            
            //遍历callbacksForURL数组中的所有字典,执行SDWebImageDownloaderCompletedBlock回调
            for (NSDictionary *callbacks in callbacksForURL) {
                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                if (callback) callback(image, data, error, finished);
            }
        } cancelled:^{
            SDWebImageDownloader *sself = wself;
            if (!sself) return;
            
            //把当前的url从URLCallbacks字典中移除
            dispatch_barrier_async(sself.barrierQueue, ^{
                [sself.URLCallbacks removeObjectForKey:url];
            });
        }];
        //设置是否需要解码
        operation.shouldDecompressImages = wself.shouldDecompressImages;
        
        //身份认证
        if (wself.urlCredential) {
            operation.credential = wself.urlCredential;
        } else if (wself.username && wself.password) {
            //设置 https 访问时身份验证使用的凭据
            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
        }
        
        //判断下载策略是否是高优先级的或低优先级,以设置操作的队列优先级
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        
        //把下载操作添加到下载队列中
        //该方法会调用operation内部的start方法开启图片的下载任务
        [wself.downloadQueue addOperation:operation];
        
        //判断任务的执行优先级,如果是后进先出,则调整任务的依赖关系,优先执行当前的(最后添加)任务
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [wself.lastAddedOperation addDependency:operation];
            
            wself.lastAddedOperation = operation;//设置当前下载操作为最后一个操作
        }
    }];
    return operation;
}

SDImageCache缓存

SDWebImage实现了内存缓存和磁盘缓存,内存缓存是通过NSCache实现,磁盘缓存是通过NSFileManager来实现文件的存储,磁盘缓存是异步实现的。
核心的方法就是对图片缓存的增、删、查的实现

/**
 * 使用指定的命名空间实例化一个新的缓存存储
 * @param ns 缓存存储使用的命名空间
 */
- (id)initWithNamespace:(NSString *)ns;
/**
 * 使用指定的命名空间实例化一个新的缓存存储和目录
 * @param  ns        缓存存储使用的命名空间
 * @param  directory 缓存映像所在目录
 */
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
//设置磁盘缓存路径
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace;
/*
 * 如果希望在 bundle 中存储预加载的图像,可以添加一个只读的缓存路径
 * 让 SDImageCache 从 Bundle 中搜索预先缓存的图像
 * @param path 只读缓存路径(mainBundle中的全路径)
 */
- (void)addReadOnlyCachePath:(NSString *)path;
/**

 * 使用指定的键将图像保存到内存和磁盘缓存
 *
 * @param image 要保存的图片
 * @param key   唯一的图像缓存键,通常是图像的完整 URL
 */
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
/**
 * 使用指定的键将图像保存到内存和可选的磁盘缓存
 *
 * @param image  要保存的图片
 * @param key    唯一的图像缓存键,通常是图像的完整 URL
 * @param toDisk 如果是 YES,则将图像缓存到磁盘
 */
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
 * 使用指定的键将图像保存到内存和可选的磁盘缓存
 *
 * @param image       要保存的图像
 * @param recalculate 是否直接使用 imageData,还是从 UIImage 重新构造数据
 * @param imageData   从服务器返回图像的二进制数据,表示直接保存到磁盘
                      而不是将给定的图像对象转换成一个可存储/可压缩的图像格式,从而保留图片质量并降低 CPU 开销
 * @param key         唯一的图像缓存键,通常是图像的完整 URL
 * @param toDisk      如果是 YES,则将图像缓存到磁盘
 */
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
 * 异步查询磁盘缓存
 *
 * @param key         保存图像的唯一键
 * @param doneBlock   查询结束后的回调
 */
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
/**
 * 同步查询内存缓存
 *
 * @param key  保存图像的唯一键
 */
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
/**
 * 查询内存缓存之后同步查询磁盘缓存
 *
 * @param key  保存图像的唯一键
 */
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
/**
 * 同步从内存和磁盘缓存删除图像
 *
 * @param key  保存图像的唯一键
 */
- (void)removeImageForKey:(NSString *)key;
/**
 * 同步从内存和磁盘缓存删除图像
 *
 * @param key        保存图像的唯一键
 * @param completion 当图片被删除后会调用该block块
 */
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
/**
 * 同步从内存和可选磁盘缓存删除图像
 *
 * @param key       保存图像的唯一键
 * @param fromDisk  如果是 YES,则从磁盘删除缓存
 */
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
/**
 * 同步从内存和可选磁盘缓存删除图像
 *
 * @param key         保存图像的唯一键
 * @param fromDisk    如果是 YES,则从磁盘删除缓存
 * @param completion  当图片被删除后会调用该block块
 */
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
/**
 * 删除所有内存缓存的图像
 */
- (void)clearMemory;
/**
 * 删除所有磁盘缓存的图像。
 * @param completion 删除操作后的块代码回调(可选)
 */
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
/**
 * 删除所有磁盘缓存的图像
 * @see clearDiskOnCompletion:方法
 */
- (void)clearDisk;
/**
 * 从磁盘中删除所有过期的缓存图像。
 * @param completion 删除操作后的块代码回调(可选)
 */
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
/**
 * 从磁盘中删除所有过期的缓存图像
 * @see cleanDiskWithCompletionBlock:方法
 */
- (void)cleanDisk;
/**
 * 获得磁盘缓存占用空间
 */
- (NSUInteger)getSize;
/**
 * 获得磁盘缓存图像的个数
 */
- (NSUInteger)getDiskCount;
/**
 * 异步计算磁盘缓存的大小
 */
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
/**
 *  异步检查图像是否已经在磁盘缓存中存在(不加载图像)
 *  @param key              保存图像的唯一键
 *  @param completionBlock  当图片被删除后会调用该block块
 *  @note  completionBlock总是在主线程
 */
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
 *  检查图像是否已经在磁盘缓存中存在(不加载图像)
 *
 *  @param key 保存图像的唯一键
 *  @return 如果该图片存在,则返回YES
 */
- (BOOL)diskImageExistsWithKey:(NSString *)key;
/**
 * 获得指定 key 对应的缓存路径(需要指定缓存路径的根目录)
 *
 * @param key  键(可以调用cacheKeyForURL方法获得)
 * @param path 缓存路径根文件夹
 */
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;
/**
 *  获得指定 key 的默认缓存路径
 *
 *  @param key  键(可以调用cacheKeyForURL方法获得)
 *
 *  @return 默认缓存路径
 */
- (NSString *)defaultCachePathForKey:(NSString *)key;

UIImageView+WebCache方法入口

UIImageView+WebCache是SDWebImage方法的入口,其中最基本的方法是
所有设置图片调用的方法最终都会调用这个方法

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;

方法解析

//所有设置图片调用的方法最终都会调用这个方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_cancelCurrentImageLoad];//取消先前的下载任务
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//动态添加属性

    if (!(options & SDWebImageDelayPlaceholder)) {
        //如果SDWebImageDelayPlaceholder为nil
        dispatch_main_async_safe(^{
            self.image = placeholder;//设置占位图
        });
    }
    //url不为nil
    if (url) {
        // check if activityView is enabled or not
        if ([self showActivityIndicatorView]) {
            //是否显示进度条
            [self addActivityIndicator];
        }
        //下载操作
        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
         
            [wself removeActivityIndicator];//移除进度条
            if (!wself) return;//self是否被释放
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {//不要自动设置图片,那么调用Block传入UIImage对象
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    //设置图片
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        //设置占位图片
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    //调用Block
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        //将生成的加载操作赋给UIView的自定义属性
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];//给添加的属性赋值
    } else {
        dispatch_main_async_safe(^{
            //错误处理
            [self removeActivityIndicator];
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            if (completedBlock) {
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容