美文网首页iOS开发指南iOS学习笔记iOS Developer
iOS 框架注解—「SDWebImage 图片下载」

iOS 框架注解—「SDWebImage 图片下载」

作者: 简晰333 | 来源:发表于2017-04-21 01:15 被阅读649次

引导


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

本篇文章主要从 [SDWebImage(Bundle version 3.7.5 -> 4.3.3) 层次结构 / 场景思维 / 总结笔记] 整理,该模块将系统化学习,后续替换、补充文章内容 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出槽点,以提高文章质量@CoderLN著;

目录:

  1. 释义
  2. 层次结构
    1、SDWebImageClass Architecture
    2、UIView+WebCache.m
    2、SDWebImageManager.m
    3、SDImageCache.m
    4、SDWebImageDownloader.m
  3. 总结笔记
    1、基本使用
    2、原理
    3、内部细节
  4. 场景思维
    1、API 接口设计
    2、线程安全
    3、回调设计
  5. SourceCodeToolsClassPublic-Codeidea

释义

This library provides an async image downloader with cache support. For convenience, we added categories for UI elements like UIImageView, UIButton, MKAnnotationView.

官方释义:
这个库提供了一个支持缓存的异步图像下载器。为方便起见,我们增加了像UI元素类别 UIImageView,UIButton,MKAnnotationView。

层次结构

SDWebImageClass Architecture

这里引用官方一张图解:4.3.3


注解:

SDWebImage 其主要层次结构有,最上层(UIView (WebCache) 类别)、逻辑层(SDWebImageManager 管理者)、业务层(SDImageCache 缓存 + SDWebImageDownloader 下载)组成。

通过对UIImageView的类别扩展来实现异步加载图片的工作。主要用到的对象:

  • 1、UIImageView (WebCache)类别,入口封装,实现读取图片完成后的回调

  • 2、SDWebImageManager,图片的管理者,记录那些图片正在读取下载。向下层读取Cache(调用SDImageCache),或向网络读取对象(调用SDWebImageDownloader)。实现SDImageCacheSDWebImageDownloader的回调。

  • 3、SDImageCache,根据 URLKEY 的MD5摘要对图片进行存储和读取(内存、磁盘),及实现图片和内存清理工作。

  • 4、SDWebImageDownloader,根据URL向网络下载数据;实现部分读取(下载选项为渐进式下载progressDownload)和全部读取后再通知回调两种方式。

.h文件

1、UIView+WebCache.m
#warning - 以下为功能模块相关的方法示例, 具体方法作用、使用、注解请移步 -> github.com/CoderLN/Framework-Codeidea


- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                        operationKey:nil
                       setImageBlock:nil
                            progress:progressBlock
                           completed:completedBlock];
}

#pragma mark - ↑
#pragma mark - 最上层:UIView+WebCache; Bundle version 4.3.3
#pragma mark - 核心代码:读取下载图片 (所有外部API sd_setImageWithURL:入口方法都将会汇总到这,只是传递的参数不同而已)

/**
 * @param url            图片地址链接
 * @param placeholder    占位图
 * @param options        下载图片的枚举。包括优先级、是否写入硬盘等
 * @param operationKey   一个记录当前对象正在加载操作的key、保证只有最新的操作在进行、默认为类名。
 * @param setImageBlock  给开发者自定义set图片的callback
 * @param progressBlock  下载进度callback
          receivedSize   已经下载的数据大小
          expectedSize   要下载图片的总大小
          targetURL      URL地址
 * @param completedBlock 下载完成的callback(sd已经给你set好了、只是会把图片给你罢了)
          image          请求的 UIImage,如果出现错误image参数是nil
          error          如果图片下载成功则error为nil,否则error有值
          cacheType      图片缓存类型(TypeNone:网络下载、TypeDisk:使用磁盘缓存、TypeMemory:使用内存缓存)
          imageURL       URL地址
 * @param context         一些额外的上下文字典
 */
- (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 *)context {
     
    // 以当前实例的class作为OperationKey
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    // 取消当前OperationKey下正在进行的操作。
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    // SD会把这个 URL 通过运行时 objc_setAssociatedObject 的方法绑定到这个 UIView 中
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    // 首先判断如果传入的下载选项options不是SDWebImageDelayPlaceholder 延迟显示占位图片,那么在主线程中设置占位图片。
    if (!(options & SDWebImageDelayPlaceholder)) {
        if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
            dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
            dispatch_group_enter(group);
        }
        //到主线城更新UI
        dispatch_main_async_safe(^{
            //set 占位图
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    
    
    if (url) {//如果url不为空
        // 首先先检查 activityView 是否可用,可用的话给 ImageView 正中间添加一个活动指示器并旋转,加载图片完成或失败都会清除掉
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
        
        // 允许开发者指定一个manager来进行操作
        SDWebImageManager *manager;
        if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
            manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
        } else {
            manager = [SDWebImageManager sharedManager];
        }
        
        __weak __typeof(self)wself = self;// 避免循环引用
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            //图片下载||读取完成
            __strong __typeof (wself) sself = wself;
            //移除小菊花
            [self sd_removeActivityIndicator];
            if (!sself) { return; }
            
            //是否不插入图片
            //如果图片下载完成,且传入的下载选项为手动设置图片则直接执行completedBlock回调,并返回
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //如果没有得到图像
            //如果传入的下载选项为延迟显示占位图片,则设置占位图片到UIImageView上面,并刷新重绘视图
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                //
                if (!sself) { return; }
                if (!shouldNotSetImage) {
                    [sself sd_setNeedsLayout];//并刷新重绘视图
                }
                if (completedBlock && shouldCallCompletedBlock) {
                    //操作完成的回调
                    completedBlock(image, error, cacheType, url);
                }
            };
            
            
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {
                //如果不显示图片、直接回调。
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }
            
            // 自动插入图片 //
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }
            
            if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
                dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
                dispatch_group_enter(group);
                dispatch_main_async_safe(^{
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                });
                // ensure completion block is called after custom setImage process finish
                dispatch_group_notify(group, dispatch_get_main_queue(), ^{
                    callCompletedBlockClojure();
                });
            } else {
                dispatch_main_async_safe(^{
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    callCompletedBlockClojure();
                });
            }
        }];
        
        // 记录当前操作:在读取图片之前,将operation存到ImageView的 SDOperationsDictionary中,为前面取消当前OperationKey下正在进行的操作存储。
        // typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        
    } else {
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}
2、SDWebImageManager.m
#warning - 以下为功能模块相关的方法示例, 具体方法作用、使用、注解请移步 -> github.com/CoderLN/Framework-Codeidea


#pragma mark - ↑
#pragma mark - 逻辑层:SDWebImageManager;Bundle version 4.3.3
#pragma mark - 加载图片核心方法;调度图片的下载(Downloader)和缓存(Cache),并不依托于 UIView+WebCache,完全可单独使用。

/**
 * @param url            图片地址链接
 * @param options        下载图片的枚举。包括优先级、是否写入硬盘等
 * @param progressBlock  下载进度callback
 * @param completedBlock 下载完成的callback
          data           图片的二进制数据
          finished
                        1.如果图像下载完成、或没有使用SDWebImageDownloaderProgressiveDownload 则为YES 1
                        2.如果使用了 SDWebImageDownloaderProgressiveDownload 渐进式下载选项,此block会被重复调用
                            1)下载完成前,image 参数是部分图像,finished 参数是 NO 0
                            2)最后一次被调用时,image 参数是完整图像,而 finished 参数是 YES
                            3)如果出现错误,那么finished 参数也是 YES
 *
 * @return SDWebImageOperation对象
 */
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    //没有completedblock,那么调用这个方法是毫无意义的
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
    
    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    //检查用户传入的URL是否正确,如果该URL是NSString类型的,那么尝试转换
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    
    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    //防止因参数类型错误而导致应用程序崩溃,判断URL是否是NSURL类型的,如果不是则直接设置为nil
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    
    //初始化一个下载操作的对象
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;
    
    BOOL isFailedUrl = NO;//初始化设定该URL是正确的
    if (url) {
        //加互斥锁,检索请求图片的URL是否在曾下载失败的集合中(URL黑名单)
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];//线程安全s
        }
    }
    
    //url为空 || (未设置失败重试 && 这个url已经失败过)
    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、也可以自定义mananger的相关block
    NSString *key = [self cacheKeyForURL:url];
    
    SDImageCacheOptions cacheOptions = 0;
    if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
    if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
    if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
    
    __weak SDWebImageCombinedOperation *weakOperation = operation;// 防止循环引用
   
    //查找URLKEY对应的图片缓存是否存在,查找完毕之后把该图片(存在|不存在)和该图片的缓存方法以block的方式传递
    //缓存情况查找完毕之后,在block块中进行后续处理(如果该图片没有缓存·下载|如果缓存存在|如果用户设置了下载的缓存策略是刷新缓存如何处理等等)
    
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        __strong __typeof(weakOperation) strongOperation = weakOperation;
        //如果被取消则把当前操作从runningOperations数组中移除,并直接返回
        if (!strongOperation || strongOperation.isCancelled) {
            [self safelyRemoveOperationFromRunning:strongOperation];
            return;
        }
        
        // Check whether we should download image from network
        //(图片不存在||下载策略为刷新缓存)且(shouldDownloadImageForURL不能响应||该图片存在缓存)
        BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
        && (!cachedImage || options & SDWebImageRefreshCached)
        && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
        if (shouldDownload) {
            //从此处开始,一直在处理downloaderOptions(即下载策略)
            //如果图像存在,但是下载策略为刷新缓存,则通知缓存图像并尝试重新下载
            if (cachedImage && options & SDWebImageRefreshCached) {
                // If image was found in the cache but 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.
                //先回调出去本地图片。再继续下载操作
                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
            
            // download if no image or requested to refresh anyway, and download allowed by delegate
            //下面是根据调用者传进来的option,来匹配设置了哪些,就给downloaderOptions赋值哪些option
            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
                //忽略从NSURLCache读取图片
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            //到此处位置,downloaderOptions(即下载策略)处理操作结束
            
            
            // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle
            // 核心方法:使用下载器,下载图片
            __weak typeof(strongOperation) weakSubOperation = strongOperation;
            strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
                if (!strongSubOperation || strongSubOperation.isCancelled) {//如果此时操作被取消,那么什么也不做
                    // Do nothing if the operation was cancelled
                    // See #699 for more details
                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
                } else if (error) {//如果下载失败,则处理结束的回调,在合适的情况下把对应图片的URL添加到黑名单中
                    [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url];
                    BOOL shouldBlockFailedURL;
                    // Check whether we should block failed url
                    if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
                        shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
                    } else {
                        shouldBlockFailedURL = (   error.code != NSURLErrorNotConnectedToInternet
                                                && error.code != NSURLErrorCancelled
                                                && error.code != NSURLErrorTimedOut
                                                && error.code != NSURLErrorInternationalRoamingOff
                                                && error.code != NSURLErrorDataNotAllowed
                                                && error.code != NSURLErrorCannotFindHost
                                                && error.code != NSURLErrorCannotConnectToHost
                                                && error.code != NSURLErrorNetworkConnectionLost);
                    }
                    
                    if (shouldBlockFailedURL) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];//失败记录
                        }
                    }
                }
                else {
                    if ((options & SDWebImageRetryFailed)) {//失败重新下载
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];//从失败记录移除
                        }
                    }
                    
                    //是否磁盘缓存
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    
                    // We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale.
                    if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
                        //缩放
                        downloadedImage = [self scaledImageForKey:key image:downloadedImage];
                    }
                    
                    //如果下载策略为SDWebImageRefreshCached且该图片缓存中存在且未下载下来,那么什么都不做
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    }
                    
                    //是否需要转换图片
                    //成功下载图片、自定义实现了图片处理的代理
                    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        //开子线程处理(异步全局队列)
                        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];
                                NSData *cacheData;
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                if (self.cacheSerializer) {
                                    cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url);
                                } else {
                                    cacheData = (imageWasTransformed ? nil : downloadedData);
                                }
                                //用户处理的后若未生成新的图片、则保存下载的二进制文件。
                                //不然则由imageCache内部生成二进制文件保存
                                [self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            
                            //回调
                            [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                
                    } else {//下载成功且未自定义代理--默认保存写入缓存 && 磁盘
                        if (downloadedImage && finished) {
                            if (self.cacheSerializer) {
                                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                                    NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
                                    [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
                                });
                            } else {
                                [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                        }
                        [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                
                if (finished) {
                    //移除当前操作
                    [self safelyRemoveOperationFromRunning:strongSubOperation];
                }
            }];
        } else if (cachedImage) {
            // 本地有图片缓存--在主线程回调、移除当前操作
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:strongOperation];
        } else {
            // Image not in cache and download disallowed by delegate
            // 本地没有图片缓存且不允许代理下载--在主线程回调、移除当前操作
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:strongOperation];
        }
    }];
    
    return operation;
}
3、SDImageCache.m
#warning - 以下为功能模块相关的方法示例, 具体方法作用、使用、注解请移步 -> github.com/CoderLN/Framework-Codeidea

#pragma mark - ↑
#pragma mark - 业务层
#pragma mark - 缓存&&磁盘操作:SDImageCache;Bundle version 4.3.3

/*
 检查要下载图片的缓存情况
    1.先检查是否有内存缓存;如果有内存缓存,再从磁盘读取diskData一起回调;
    2.如果没有内存缓存则检查是否有沙盒缓存
    3.如果有沙盒缓存,则把该图片写入内存缓存并处理doneBlock回调
 */
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    //如果缓存对应的key为空,则直接返回,并把存储方式(无缓存)通过block块以参数的形式传递
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // First check the in-memory cache...
    // 检查该 URLKEY 对应的内存缓存,如果存在内存缓存,再从磁盘读取diskData一起回调;并把图片和存储方式(内存缓存)通过block块以参数的形式传递
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if (image.images) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
       
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }
    
    NSOperation *operation = [NSOperation new];//创建一个操作
    
    //使用异步函数,添加任务到串行队列中(会开启一个子线程处理block块中的任务)
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {//如果当前的操作被取消,则直接返回
            // do not call the completion if cancelled
            return;
        }
        
        @autoreleasepool {
            // 检查该KEY对应的磁盘缓存
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            //如果存在磁盘缓存,且应该把该图片保存一份到内存缓存中,则先计算该图片的cost(成本)并把该图片保存到内存缓存中
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                //使用NSChache缓存。
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
            if (doneBlock) {
                //线程间通信,在主线程中回调doneBlock,并把图片和存储方式(磁盘缓存)通过block块以参数的形式传递
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });
    return operation;
}
4、SDWebImageDownloader.m
#warning - 以下为功能模块相关的方法示例, 具体方法作用、使用、注解请移步 -> github.com/CoderLN/Framework-Codeidea

#pragma mark - ↑
#pragma mark - 下载操作:SDWebImageDownloader;Bundle version 4.3.3

//核心方法:下载图片的操作
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    __weak SDWebImageDownloader *wself = self;//为了避免block的循环引用
    
    //处理进度回调|完成回调等,如果该url在self.URLCallbacks并不存在,则调用createCallback block块
    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{//创建下载operation
        __strong __typeof (wself) sself = wself;
        NSTimeInterval timeoutInterval = sself.downloadTimeout;//处理下载超时,如果没有设置过则初始化为15秒
        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
        //创建下载策略
        /*
         NSURLRequestUseProtocolCachePolicy:默认的缓存策略
             1)如果缓存不存在,直接从服务端获取。
             2)如果缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端.
         NSURLRequestReloadIgnoringLocalCacheData:忽略本地缓存数据,直接请求服务端下载。
         */
        NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        //创建下载请求
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];
        
        //设置是否使用Cookies(采用按位与)
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        //开启HTTP管道,这可以显著降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的
        request.HTTPShouldUsePipelining = YES;
        //设置请求头信息(过滤等)
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]);
        }
        else {
            request.allHTTPHeaderFields = [sself allHTTPHeaderFields];
        }
        
        // 创建下载操作
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        //设置是否需要解码
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        
        //身份认证(证书)
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            //设置 https 访问时身份验证使用的凭据(默认 账号密码为空的通用证书)
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        
        //判断下载策略是否是高优先级的或低优先级,以设置操作的队列优先级
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        
        //判断任务的执行优先级,如果是后进先出,则调整任务的依赖关系,优先执行当前的(最后添加)任务
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }
        
        return operation;
    }];
}

注解1:
SDWebImageOptions类型: 使用位移枚举,通过按位与&按位或|的组合方式传递多个值

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    
    SDWebImageRetryFailed = 1 << 0, //失败后尝试重新下载
    
    SDWebImageLowPriority = 1 << 1, //低优先级;图像下载会推迟到滚动视图停止滚动之后再继续下载。
    
    SDWebImageCacheMemoryOnly = 1 << 2, //只使用内存缓存
    
    SDWebImageProgressiveDownload = 1 << 3, //渐进式下载;就像浏览器中那样,下载过程中,图像会逐步显示出来。
    
    SDWebImageRefreshCached = 1 << 4,   //刷新缓存;此选项用于处理URL指向图片发生变化的情况,被刷新会调用一次 completion block,并传递最终的图像。
  
    SDWebImageContinueInBackground = 1 << 5,    //后台下载;当App进入后台后仍然会继续下载图像,如果后台任务过期,请求将会被取消。
  
    SDWebImageHandleCookies = 1 << 6,   //处理保存在NSHTTPCookieStore中的cookies
 
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,     //允许不信任的 SSL 证书;可以出于测试目的使用,在正式产品中慎用
    
    SDWebImageHighPriority = 1 << 8,    //高优先级(优先下载);此标记会将它们移动到队列前端被立即加载
    
    SDWebImageDelayPlaceholder = 1 << 9,    //延迟占位图片;此标记会延迟加载占位图像,直到图像已经完成加载
 
    SDWebImageTransformAnimatedImage = 1 << 10, //转换动画图像;通常不会在可动画的图像上调用transformDownloadedImage代理方法,因为大多数转换代码会破坏动画文件
 
    SDWebImageAvoidAutoSetImage = 1 << 11   //手动设置图像;下载完成后手动设置图片,默认是下载完成后自动放到ImageView上

    SDWebImageScaleDownLargeImages = 1 << 12,  //图像解码缩小
    
    SDWebImageQueryDataWhenInMemory = 1 << 13,  //强制同步查询内存缓存数据
    
    SDWebImageQueryDiskSync = 1 << 14,  //
    
    SDWebImageFromCacheOnly = 1 << 15,  //
   
    SDWebImageForceTransition = 1 << 16  //
};

注解2:
位移运算符语法

1、按位与"&"
只有对应的两个二进位均为1时,结果位才为1,否则为0
例如 9&5,其实就是 1001 & 0101=0001,等于1 

2、按位或"|"
只要对应的两个二进位有一个为1时,结果位就为1,否则为0。
例如 9|5,其实就是 1001 | 0101=1101,等于13

3、左移"<<"
把整数a的各二进位全部左移n位,高位丢弃,低位补0。左移n位其实就是乘以2的n次方。
例如 1<<2 就是 0001左移2=0100,等于4

总结笔记

基本使用

  • 下载设置图片且需要获取下载进度
/*
 * 图片下载的核心方法
    需求1:下载设置图片且需要获取下载进度
    解决: 包含 #import "UIImageView+WebCache.h";
    采用 `- sd_setImageWithURL:placeholderImage:options:progress:completed:`(异步下载并缓存)
 *
 * @param url               URL地址
 * @param placeholder       先使用占位图片,当图片加载完成后再替换.
 * @param options           图片下载选项,默认SDWebImageRetryFailed失败后重新连接(参考SDWebImageOptions位移枚举);
 * @param progressBlock     下载进度回调
          receivedSize      已经下载的数据大小
          expectedSize      要下载图片的总大小
          targetURL         URL地址
 * @param completedBlock    操作成功回调
          image             请求的 UIImage,如果出现错误image参数是nil
          error             如果图片下载成功则error为nil,否则error有值
          cacheType         图片缓存类型(TypeNone:网络下载、TypeDisk:使用磁盘缓存、TypeMemory:使用内存缓存)
          imageURL:        URL地址
 */
- (void)download11 {
 
    [self.imageView sd_setImageWithURL:requestUrl placeholderImage:[UIImage imageNamed:@"pbw"] options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        
        NSLog(@"progress %f \n targetURL %@",1.0 * receivedSize / expectedSize,targetURL);
    } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
        
        NSLog(@"download11-- %@",[NSThread currentThread]);// {number = 1, name = main}
        self.imageView.image = image;
 
        switch (cacheType) {
            case SDImageCacheTypeNone:
                NSLog(@"网络下载");
                break;
            case SDImageCacheTypeMemory:
                NSLog(@"使用内存缓存");
                break;
            case SDImageCacheTypeDisk:
                NSLog(@"使用磁盘缓存");
                break;
                
            default:
                break;
        }
    }];
}

  • 只需要获得一张图片
/*
 * 图片下载的核心方法
        需求2:只需要获得一张图片
        解决: 包含 #import "SDWebImageManager.h"
              采用 `- loadImageWithURL:options:progress:completed:`(异步下载并缓存)
 */
- (void)download22
{
    [[SDWebImageManager sharedManager] loadImageWithURL:requestUrl options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        
        // 这里为什么不会调用 ?
        NSLog(@"progress %f \n targetURL %@",1.0 * receivedSize / expectedSize,targetURL);
    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
        
        NSLog(@"download22-- %@",[NSThread currentThread]);// {number = 1, name = main}
        NSLog(@"imageURL %@ \n finished%d",imageURL,finished);
        self.imageView.image = image;
        
        switch (cacheType) {
            case SDImageCacheTypeNone:
                NSLog(@"网络下载");
                break;
            case SDImageCacheTypeMemory:
                NSLog(@"使用内存缓存");
                break;
            case SDImageCacheTypeDisk:
                NSLog(@"使用磁盘缓存");
                break;
                
            default:
                break;
        }
    }];
}

  • 图片下载不需要任何的缓存处理
/*
 * 图片下载的核心方法
        需求3:图片下载不需要任何的缓存处理
        解决: 包含 #import "SDWebImageDownloader.h"
              采用 `- downloadImageWithURL:options:progress:completed:`(内部不做缓存处理)
 */
- (void)download33
{
    [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:requestUrl options:SDWebImageDownloaderProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        
        NSLog(@"progress %f \n targetURL %@",1.0 * receivedSize / expectedSize,targetURL);
    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
        
        NSLog(@"download33-- %@",[NSThread currentThread]);// {number = 1, name = main}
        NSLog(@"finished %d",finished);
        
        self.imageView.image = image;
    }];
}

  • 播放Gif图片、判断图片类型
播放Gif图片
self.imageView.image = [UIImage sd_animatedGIFWithData:imageData];

判断图片类型
NSInteger imaegFormat = [NSData sd_imageFormatForImageData:imageData];

 typedef NS_ENUM(NSInteger, SDImageFormat) {
    SDImageFormatUndefined = -1,
    SDImageFormatJPEG = 0,
    SDImageFormatPNG,
    SDImageFormatGIF,
    SDImageFormatTIFF,
    SDImageFormatWebP,
    SDImageFormatHEIC
 };
原理
  • 工作流程


  • 最上层:UIView+WebCache下载核心方法


  • 逻辑层:SDWebImageManager调度下载和缓存


  • 业务层:SDImageCache缓存&Downloader磁盘操作


内部细节
  • 磁盘目录位于哪里?
    #pragma mark - SDImageCache.m
 
    缓存在磁盘沙盒目录下 Library/Caches
    二级目录为~/Library/Caches/default/com.hackemist.SDWebImageCache.default

    //设置磁盘缓存路径
    -(NSString *)makeDiskCachePath:(NSString*)fullNamespace{
        //获得caches路径,该框架内部对图片进行磁盘缓存,设置的缓存目录为沙盒中Library的caches目录下
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        //在caches目录下,新建一个名为【fullNamespace】的文件,沙盒缓存就保存在此处
        return [paths[0] stringByAppendingPathComponent:fullNamespace];
    }
 
     //使用指定的命名空间实例化一个新的缓存存储和目录
     - (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
        if ((self = [super init])) {
            //拼接默认的磁盘缓存目录
            NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
        }
     }

    //你也可以通过下面方法来自定义一个路径。但这个路径不会被存储使用、是给开发者自定义预装图片的路径。
    [[SDImageCache sharedImageCache] addReadOnlyCachePath:bundledPath];
  • 最大并发数量、超时时长s
    _downloadQueue = [NSOperationQueue new];        //创建下载队列:非主队列(在该队列中的任务在子线程中异步执行)
    _downloadQueue.maxConcurrentOperationCount = 6; //设置下载队列的最大并发数:默认为6
    _downloadTimeout = 15.0;
  • 默认的最大缓存时间为1周
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
  • 缓存文件的保存名称如何处理?
    1.写入缓存时、直接用图片url作为key
    NSUInteger cost = SDCacheCostForImage(image);
    [self.memCache setObject:image forKey:key cost:cost];


    2.写入磁盘时,对key(通常为URL)进行MD5加密,加密后的密文作为图片的名称,可以防止文件名过长。
    - (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
        const char *str = key.UTF8String;
        if (str == NULL) {
        str = "";
        }
        unsigned char r[CC_MD5_DIGEST_LENGTH];
        CC_MD5(str, (CC_LONG)strlen(str), r);
        NSURL *keyURL = [NSURL URLWithString:key];
        NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
        NSString *filename = [NSString stringWithFormat:
                            @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                            r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                            r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
        return filename;
    }
  • 如何判断图片的类型?
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    //在判断图片类型的时候,只匹配NSData数据第一个字节。
    // File signatures table: http://www.garykessler.net/library/file_sigs.html
    uint8_t c;
    [data getBytes:&c length:1];//获得传入的图片二进制数据的第一个字节
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52: {
            if (data.length >= 12) { //WEBP :是一种同时提供了有损压缩与无损压缩的图片文件格式
                //RIFF....WEBP
                //获取前12个字节
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
                //如果以『RIFF』开头,且以『WEBP』结束,那么就认为该图片是Webp类型的
                if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                    return SDImageFormatWebP;
                }
            }
            break;
        }
        case 0x00: {
            if (data.length >= 12) {
                //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
                if ([testString isEqualToString:@"ftypheic"]
                    || [testString isEqualToString:@"ftypheix"]
                    || [testString isEqualToString:@"ftyphevc"]
                    || [testString isEqualToString:@"ftyphevx"]) {
                    return SDImageFormatHEIC;
                }
            }
            break;
        }
    }
    return SDImageFormatUndefined;
}
  • 图片缓存类型?
 SDImageCacheType cacheType(TypeNone:网络下载、TypeDisk:使用磁盘缓存、TypeMemory:使用内存缓存)
  • 框架内部对内存警告的处理方式?
        // Subscribe to app events
        //监听应用程序通知
        
        // init方法 监听到(应用程序发生内存警告)通知,调用didReceiveMemoryWarning方法,移除所有内存缓存;
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(didReceiveMemoryWarning:)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
        
        //当监听到(程序将终止)调用deleteOldFiles方法,清理过期文件(默认大于一周)的磁盘缓存
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
        //当监听到(进入后台),调用backgroundDeleteOldFiles方法,清理未完成、长期运行的任务缓存
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
  • 该框架进行缓存处理的方式?
    可变字典(以前) ---> (SDMemoryCache: NSCache) 缓存处理

    cache.totalCostLimit = 5;// 设置最大缓存控件的总成本, 如果发现存的数据超过中成本那么会自动回收之前的对
    cache.countLimit = 5;// 设置最大缓存文件的数量, 示例:多图下载综合案例
  • 队列中任务的处理方式?
    @property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder; //通过该属性,可以修改下载操作执行顺序
    _executionOrder = SDWebImageDownloaderFIFOExecutionOrder; //下载任务的执行方式:所有下载操作将按照队列的先进先出方式执行
  • SDWebImageDownloader 如何下载图片?
   发送网络请求下载图片,NSURLSession (NSURLSessionDataDelegate);
  • 磁盘清理的原则?
    程序将终止
    首先移除早于过期日期的文件(kDefaultCacheMaxCacheAge = 1 week)。
    其次如果剩余磁盘缓存空间超出最大限额,则按时间排序再次执行清理操作,循环依次删除最早的文件,直到低于期望的缓存限额的 1/2 (currentCacheSize < self.maxCacheSize / 2)。

    内存警告
    如果发生内存警告会收到通知,对应调用didReceiveMemoryWarning:方法,直接把把所有的内存缓存都删除。

    程序进入后台
    如果程序进入后台会收到通知,对应调用backgroundDeleteOldFiles方法,清理未完成、长期运行的任务task 缓存。

场景思维

API 接口设计

#import "UIImageView+WebCache.h"
#import "UIButton+WebCache.h"
#import "UIImageView+HighlightedWebCache.h"

#import "UIView+WebCache.h" 

所有外层API与具体业务无关,使得SDWebImageManager可以脱离View层单独运作。

线程安全

//加互斥锁,检索请求图片的URL是否在曾下载失败的集合中(URL黑名单)
@synchronized (self.failedURLs) {
    isFailedUrl = [self.failedURLs containsObject:url];//线程安全s
}

if ((options & SDWebImageRetryFailed)) {//失败重新下载
    @synchronized (self.failedURLs) {
        [self.failedURLs removeObject:url];//从失败记录移除
    }
} 

所有可能引起资源抢夺的对象操作、全部有条件锁保护。

回调设计

SDWebImage中使用了两种、Block以及Delegate。

  • Block
    单个图片的分类、单个图片的下载。
    每个操作任务中必现的progress以及completed。
    所以、有很强的个体绑定需要或者使用次数不多时、倾向使用block

  • Delegate
    SDWebImageManager下载完成之后的自定义图片处理、是否下载某个url。
    这两个方法如果需要的话都是将会调用多次的。所以、用Delegate更好、可以将方法常驻。

  • UITableView的使用Delegate、是用为在滚动途中、代理方法需要被不断的执行。
    UIButton也是将会被多次点击。
    UIView的动画/GCD则可以使用Block、因为只执行一次、用完释放。
    所以、在日常使用中、我们也可以参考上述原则进行设计。

  • NSMapTable
    用NSMapTable代替字典来存储当前正在进行的操作、并且将value设置为 NSPointerFunctionsWeakMemory。防止对应value因为强引用不能自动释放。

// typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
[self sd_setImageLoadOperation:operation forKey:validOperationKey];


@implementation UIView (WebCacheOperation)
- (SDOperationsDictionary *)sd_operationDictionary {
    @synchronized(self) {
        SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
        if (operations) {
            return operations;
        }
        operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
        objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        return operations;
    }
}

最后分享一下学习多线程自定义NSOperation下载图片思路:

自定义NSOperation下载图片思路.gif

文字解析:

  • 1.根据图片的url先去检查images(内存缓存)中该图片是否存在,如果存在就直接显示到cell上;否则去检查磁盘缓存(沙盒)。

  • 2.如果有磁盘缓存(沙盒),加载沙盒中对应的图片显示到cell上,再保存一份到内存缓存;否则先显示占位图片,再检查operations(操作缓存)中该图片是否正在下载,如果是 就等待下载;否则创建下载operations操作任务,保存到操作缓存中去下载。

  • 3.下载完成后(需要主动刷新显示(采用局部刷新)),将操作从操作缓存中移除,将图片在内存缓存(先) 和 沙盒(后)中各保存一份。

参考:
https://www.jianshu.com/p/3b8a7ae966d3

Reading


  • 如果在阅读过程中遇到 Error || New ideas,希望你能 issue 我,我会及时补充谢谢。

  • 熬夜写者不易,喜欢可 赞赏 or Star 一波;点击左上角关注 或 『Public:Codeidea』,在 Demo | 文章 | Rss 更新时收到提醒通知,便捷阅读。

相关文章

  • iOS 框架注解—「SDWebImage 图片下载」

    引导 SDWebImage 是我们经常使用的一个异步图片加载库,在项目中使用SDWebImage来管理图片加载相...

  • SDWebImage

    SDWebImage介绍 iOS中著名的网络图片处理框架. 包含的功能:图片下载,图片缓存,下载进度监听,GIF处...

  • 开源第三方学习之SDWebImage

    SDWebImage 图片下载缓存框架 常用方法及原理 常见面试题: SDWebImage的最大并发数是多少? _...

  • SDWebImage面试可能问到的细节?

    『导言』 iOS开发中经常用到下载图片的第三方SDWebImage框架,进行有效的图片下载和缓存。那么对SDWeb...

  • 胡说八道 - 3 SDWebImage 见语

    SDWebImage 简要介绍 SDWebImage 是一款性能卓越、流行度高的网络图片下载框架。对 SDWebI...

  • SDWebImage窥探

    SDWebImage SDWebImage是一款图片下载缓存框架,添加到工程中不会有烦人的警告原理:SDWebIm...

  • iOS开发中常用的三方框架

    Objective-C AFNetworking :常用的网络请求框架 SDWebImage :一个异步图片下载缓...

  • SDWebImage之图片下载

    title: SDWebImage之图片下载categories: 第三方框架tags: 三方框架解析 我们经常使...

  • SDWebImage

    SDWebImage SDWebImage的简单介绍:一款世界级图片下载缓存框架,添加到工程中不会有烦人的警告 首...

  • SDWebImage源码解析

    概述 SDWebImage是一个强大的图片下载框架,利用异步加载和内存+磁盘两级缓存处理,高效优雅的解决了图片下载...

网友评论

    本文标题:iOS 框架注解—「SDWebImage 图片下载」

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