美文网首页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 图片下载」

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