美文网首页
SDWebImage深度剖析

SDWebImage深度剖析

作者: 撒花小仙女_卡卡 | 来源:发表于2019-07-11 14:16 被阅读0次

    SDWebImage 如何保证UI操作放在主线程中执行?

    在SDWebImage的SDWebImageCompat.h中有这样一个宏定义,用来保证主线程操作,为什么要这样写?

    // SDWebImageCompat.h 中
    
    #ifndef dispatch_main_async_safe
    #define dispatch_main_async_safe(block)\
        if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
            block();\
        } else {\
            dispatch_async(dispatch_get_main_queue(), block);\
        }
    #endif
    

    在此之前见到最多的是这样的:

    #define dispatch_main_async_safe(block)\
        if ([NSThread isMainThread]) {\
            block();\
        } else {\
            dispatch_async(dispatch_get_main_queue(), block);\
        }
    

    对比两段代码可以发现前者有两个地方改变了,一是多了 #ifndef,二是判断条件改变了

    显然,增加 #ifndef 是为了提高代码的严谨,防止重复定义 dispatch_main_async_safe。

    关于为什么改变判断条件,我们可参考dispatch_get_main_queue的苹果官方文档来进一步了解

    image.png

    我们知道在使用 GCD 创建一个 queue 的时候会指定 queue_label,可以理解为队列名:

    dispatch_queue_t myQueue = dispatch_queue_create("com.apple.threadQueue", DISPATCH_QUEUE_SERIAL);
    

    而第一个参数就是 queue_label,根据官方文档解释,这个queueLabel 是唯一的,所以SDWebImage就采用了这个方式

    //取得当前队列的队列名
    dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
       
    //取得主队列的队列名
    dispatch_queue_get_label(dispatch_get_main_queue())
    
    

    然后通过 strcmp 函数进行比较,如果为0 则证明当前队列就是主队列。

    //SDWebImage中的实例 :判断当前是否是IOQueue
    - (void)checkIfQueueIsIOQueue {
        const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
        const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
        if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
            NSLog(@"This method should be called from the ioQueue");
        }
    }
    
    • ⚠️ 结论⚠️
      SDWebImage 就是从判断是否在主线程执行改为判断是否由主队列上调度。而由于主队列是一个串行队列,无论任务是异步同步都不会开辟新线程,所以当前队列是主队列等价于当前在主线程上执行。可以这样说,在主队列调度的任务肯定在主线程执行,而在主线程执行的任务不一定是由主队列调度的。

    UML类结构

    SDWebImageClassDiagram.png
    • 我们可以把上面的UML图分成两大类来看:


      image.png

    在了解UML类结构前,我们先要掌握几个概念

    泛化(generalization):表示is-a的关系,是对象之间耦合度最大的一种关系,子类继承父类的所有细节。直接使用语言中的继承表达。在类图中使用带三角箭头的实线表示,箭头从子类指向父类

    image.png

    实现(Realization):在类图中就是接口和实现的关系。在类图中使用带三角箭头的虚线表示,箭头从实现类指向接口

    image.png

    依赖(Dependency) :对象直接最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系。一个类调用被依赖类中的某些方法而得以完成这个类的一下职责。在类图中使用带箭头的虚线表示,箭头从使用类指向被依赖的类

    image.png

    聚合(Aggregation):表示has-a的关系,是一种不稳定的包含关系。较强于一般关联,有整体与局部的关系,并且没有了整体,局部也可单独存在。如公司和员工的关系,公司包含员工,但如果公司倒闭,员工依然可以换公司。在类图中使用空心的菱形表示,菱形从局部指向整体is-a与has-a的区别

    image.png

    组合(Composition):表示contains-a的关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。是一种更强的聚合关系。部分不能脱离整体存在。如公司和部门的关系,没有了公司,部门也就不存在了;在调查问卷中问题和选项的关系;订单和订单选项的关系。在类图中使用实心的菱形表示,菱形从局部指向整体

    image.png
    • 聚合和组合的区别
      这两个比较难理解,重点说一下。聚合和组合的区别在于:聚合关系是“has-a”关系,组合关系是“contains-a”关系;聚合关系表示整体与部分的关系比较弱,而组合比较强;聚合关系中代表部分事物的对象与代表聚合事物的对象的生存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。组合中一旦删除了组合对象,同时也就删除了代表部分事物的对象。

    UIImageView/UIView显示流程

    • UIImageView/UIView显示流程如图所示:


      SDWebImageSequenceDiagram.png
    • UIImageView/UIView显示流程源码分析如下:

    - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                      placeholderImage:(nullable UIImage *)placeholder
                               options:(SDWebImageOptions)options
                               context:(nullable SDWebImageContext *)context
                         setImageBlock:(nullable SDSetImageBlock)setImageBlock
                              progress:(nullable SDImageLoaderProgressBlock)progressBlock
                             completed:(nullable SDInternalCompletionBlock)completedBlock {
        context = [context copy]; // copy to avoid mutable object
        //获取validOperationKey,首先判断是否有OperationKey,如果没有则直接取相应调用类的class名字
        NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
        if (!validOperationKey) {
            validOperationKey = NSStringFromClass([self class]);
        }
        self.sd_latestOperationKey = validOperationKey;
        //取消当前正在下载的任务
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
        self.sd_imageURL = url;
        //保存url并设置占位图片---通过关联对象以imageURLKey保存URL---如果是多图下载则使用dispatch_group----判断如果不是延迟设置占位图,使用dispatch_main_async_safe宏设置占位图
        dispatch_group_t group = context[SDWebImageInternalSetImageGroupKey];
        if (!(options & SDWebImageDelayPlaceholder)) {
           if (group){
                dispatch_group_enter(group);
            }
            dispatch_main_async_safe(^{
                [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
            });
        }
        
        if (url) {
            // reset the progress
            NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
            if (imageProgress) {
                imageProgress.totalUnitCount = 0;
                imageProgress.completedUnitCount = 0;
            }
            
    #if SD_UIKIT || SD_MAC
            // check and start image indicator
            [self sd_startImageIndicator];
            id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
    #endif
          //获取SDWebImageManager对象
            SDWebImageManager *manager = context[SDWebImageContextCustomManager];
            if (!manager) {
                manager = [SDWebImageManager sharedManager];
            }
            
            SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
                if (imageProgress) {
                    imageProgress.totalUnitCount = expectedSize;
                    imageProgress.completedUnitCount = receivedSize;
                }
    #if SD_UIKIT || SD_MAC
                if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
                    double progress = 0;
                    if (expectedSize != 0) {
                        progress = (double)receivedSize / expectedSize;
                    }
                    progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [imageIndicator updateIndicatorProgress:progress];
                    });
                }
    #endif
                if (progressBlock) {
                    progressBlock(receivedSize, expectedSize, targetURL);
                }
            };
            @weakify(self);
            //调用SDWebImageManager的loadImageWithURL方法获取一个加载任务
            // 调用loadImageWithURL时首先会查找一次缓存,当缓存里没有时,再进行下载
            id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                @strongify(self);
                if (!self) { return; }
                // if the progress not been updated, mark it to complete state
                if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
                    imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                    imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
                }
                
    #if SD_UIKIT || SD_MAC
                // check and stop image indicator
                if (finished) {
                    [self sd_stopImageIndicator];
                }
    #endif
                
                BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
                BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                          (!image && !(options & SDWebImageDelayPlaceholder)));
                SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                    if (!self) { return; }
                    if (!shouldNotSetImage) {
                        [self sd_setNeedsLayout];
                    }
                    if (completedBlock && shouldCallCompletedBlock) {
                        completedBlock(image, data, error, cacheType, finished, 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 SD_UIKIT || SD_MAC
                // check whether we should use the image transition
                SDWebImageTransition *transition = nil;
                if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
                    transition = self.sd_imageTransition;
                }
    #endif
                dispatch_main_async_safe(^{
    #if SD_UIKIT || SD_MAC
                    [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
    #else
                    [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
    #endif
                    callCompletedBlockClojure();
                });
            }];
            [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        } else {
    #if SD_UIKIT || SD_MAC
            [self sd_stopImageIndicator];
    #endif
            dispatch_main_async_safe(^{
                if (completedBlock) {
                    NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
                    completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
                }
            });
        }
    }
    
    - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
        if (key) {
            // Cancel in progress downloader from queue
            //NSMapTable存储---NSDictionary类似
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            id<SDWebImageOperation> operation;
            
            @synchronized (self) {
                operation = [operationDictionary objectForKey:key];
            }
            if (operation) {
                if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                    [operation cancel];
                }
                @synchronized (self) {
                    [operationDictionary removeObjectForKey:key];
                }
            }
        }
    }
    
    - (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;
        }
    }
    
    //SDWebImageManager的loadImageWithURL方法
    - (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                              context:(nullable SDWebImageContext *)context
                                             progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                            completed:(nonnull SDInternalCompletionBlock)completedBlock {
        // Invoking this method without a completedBlock is pointless
        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.
        if ([url isKindOfClass:NSString.class]) {
            url = [NSURL URLWithString:(NSString *)url];
        }
    
        // Prevents app crashing on argument type error like sending NSNull instead of NSURL
        //容错处理
        if (![url isKindOfClass:NSURL.class]) {
            url = nil;
        }
    
        SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
        operation.manager = self;
        //如果下载失败,加入黑名单
        BOOL isFailedUrl = NO;
        if (url) {
            SD_LOCK(self.failedURLsLock);
            isFailedUrl = [self.failedURLs containsObject:url];
            SD_UNLOCK(self.failedURLsLock);
        }
        //SDWebImageRetryFailed默认情况下,当一个URL下载失败,将不会继续尝试下载
        if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
            [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
            return operation;
        }
    
        SD_LOCK(self.runningOperationsLock);
        [self.runningOperations addObject:operation];
        SD_UNLOCK(self.runningOperationsLock);
        
        // Preprocess the options and context arg to decide the final the result for manager
        SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
        
        // Start the entry to load image from cache
        //磁盘缓存和内存缓存,sd优先缓存到内存缓存
        [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
    
        return operation;
    }
    
    - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
        if (!key) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return nil;
        }
        
        id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
        if (transformer) {
            // grab the transformed disk image if transformer provided
            NSString *transformerKey = [transformer transformerKey];
            key = SDTransformedKeyForKey(key, transformerKey);
        }
        
        // First check the in-memory cache...首先从内存缓存查找
        UIImage *image = [self imageFromMemoryCacheForKey:key];
    
        if ((options & SDImageCacheDecodeFirstFrameOnly) && image.sd_isAnimated) {
    #if SD_MAC
            image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
    #else
            image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
    #endif
        }
    
        BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
        if (shouldQueryMemoryOnly) {
            if (doneBlock) {
                doneBlock(image, nil, SDImageCacheTypeMemory);
            }
            return nil;
        }
        
        // Second check the disk cache...
        NSOperation *operation = [NSOperation new];
        // Check whether we need to synchronously query disk
        // 1. in-memory cache hit & memoryDataSync
        // 2. in-memory cache miss & diskDataSync
        BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                    (!image && options & SDImageCacheQueryDiskDataSync));
        void(^queryDiskBlock)(void) =  ^{
            if (operation.isCancelled) {
                // do not call the completion if cancelled
                return;
            }
            
            @autoreleasepool {
                NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
                UIImage *diskImage;
                SDImageCacheType cacheType = SDImageCacheTypeNone;
                //当前image由内存中查到到的
                if (image) {
                    // the image is from in-memory cache, but need image data
                    diskImage = image;
                    cacheType = SDImageCacheTypeMemory;
                } else if (diskData) {
                    cacheType = SDImageCacheTypeDisk;
                    // decode image data only if in-memory cache missed
                    //只有在内存缓存丢失时才解码图像数据
                    //diskData不为空,证明磁盘缓存中有数据,将数据从磁盘中取出
                    //NSData转换成UIImage时不涉及到解压缩
                    //UIImage的图片格式是png/jpeg----------png/jpeg转换成位图时 ,才涉及到解压缩的操作
                    //解压位图很耗时,将放在子线程中进行操作
                    diskImage = [self diskImageForKey:key data:diskData options:options context:context];
                    if (diskImage && self.config.shouldCacheImagesInMemory) {
                        NSUInteger cost = diskImage.sd_memoryCost;
                        //取出后将image写入到内存当中
                        [self.memoryCache setObject:diskImage forKey:key cost:cost];
                    }
                }
                
                if (doneBlock) {
                    if (shouldQueryDiskSync) {
                        doneBlock(diskImage, diskData, cacheType);
                    } else {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            doneBlock(diskImage, diskData, cacheType);
                        });
                    }
                }
            }
        };
        
        // Query in ioQueue to keep IO-safe
        if (shouldQueryDiskSync) {
            dispatch_sync(self.ioQueue, queryDiskBlock);
        } else {
            dispatch_async(self.ioQueue, queryDiskBlock);
        }
        
        return operation;
    }
    
    • 更简洁UIImageView显示流程的理解如图所示:


      6191751-2a44a73fad06cb73.png

    SDImageCache

    SDImageCache是继承自NSObject的。做了cache的一些基本配置和cache的管理。
    如cache的大小,cache的有效期,添加cache,删除cache等。

    cache的类型如下:

    typedef NS_ENUM(NSInteger, SDImageCacheType) {
        /**
         * The image wasn't available the SDWebImage caches, but was downloaded from the web.(不缓存,从web加载)
         */
        SDImageCacheTypeNone,
        /**
         * The image was obtained from the disk cache.(磁盘缓存)
         */
        SDImageCacheTypeDisk,
        /**
         * The image was obtained from the memory cache.(内存缓存)
         */
        SDImageCacheTypeMemory
    };
    

    内部的AutoPurgeCache是继承自NSCache的,主要用途是在收到系统的UIApplicationDidReceiveMemoryWarningNotification通知时,清理内存缓存。

    此外,SDWebCache的内存缓存也是使用的NSCache.
    设置缓存的核心方法如下:

    - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
        if (!image || !key) {
            return;
        }
        // if memory cache is enabled
        if (self.shouldCacheImagesInMemory) {
            NSUInteger cost = SDCacheCostForImage(image);
            [self.memCache setObject:image forKey:key cost:cost];
        }
    
        if (toDisk) {
            dispatch_async(self.ioQueue, ^{
                NSData *data = imageData;
    
                if (image && (recalculate || !data)) {
    #if TARGET_OS_IPHONE
                    // We need to determine if the image is a PNG or a JPEG
                    // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)
                    // The first eight bytes of a PNG file always contain the following (decimal) values:
                    // 137 80 78 71 13 10 26 10
    
                    // If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download)
                    // and the image has an alpha channel, we will consider it PNG to avoid losing the transparency
                    int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
                    BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                                      alphaInfo == kCGImageAlphaNoneSkipFirst ||
                                      alphaInfo == kCGImageAlphaNoneSkipLast);
                    BOOL imageIsPng = hasAlpha;
    
                    // But if we have an image data, we will look at the preffix
                    if ([imageData length] >= [kPNGSignatureData length]) {
                        imageIsPng = ImageDataHasPNGPreffix(imageData);
                    }
    
                    if (imageIsPng) {
                        data = UIImagePNGRepresentation(image);
                    }
                    else {
                        data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
                    }
    #else
                    data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
    #endif
                }
    
                [self storeImageDataToDisk:data forKey:key];
            });
        }
    }
    

    从代码可以看出,先设置的内存缓存,再设置的磁盘缓存。

    - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {
        
        if (key == nil) {
            return;
        }
    
        if (self.shouldCacheImagesInMemory) {
            [self.memCache removeObjectForKey:key];
        }
    
        if (fromDisk) {
            dispatch_async(self.ioQueue, ^{
                [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
                
                if (completion) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completion();
                    });
                }
            });
        } else if (completion){
            completion();
        }
        
    }
    
    • 删除缓存的时候也是先删除内存缓存,再删除磁盘缓存。

    UIImageView+WebCache

    通过UIImageView集成SDWebImage异步下载和缓存远程图像。
    下载图片的核心方法如下:

    - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
        [self sd_cancelCurrentImageLoad];
        objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
        if (!(options & SDWebImageDelayPlaceholder)) {
            dispatch_main_async_safe(^{ //保证是在主线程中设置图片
                self.image = placeholder;
            });
        }
        
        if (url) {
    
            // check if activityView is enabled or not
            if ([self showActivityIndicatorView]) {
                [self addActivityIndicator];
            }
    
            __weak __typeof(self)wself = self;
            id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                [wself removeActivityIndicator];
                if (!wself) return;
                dispatch_main_sync_safe(^{
                    if (!wself) return;
                    if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                    {
                        completedBlock(image, error, cacheType, url);
                        return;
                    }
                    else if (image) {
                        wself.image = image;
                        [wself setNeedsLayout];
                    } else {
                        if ((options & SDWebImageDelayPlaceholder)) {
                            wself.image = placeholder;
                            [wself setNeedsLayout];
                        }
                    }
                    if (completedBlock && finished) {
                        completedBlock(image, error, cacheType, url);
                    }
                });
            }];
            [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
        } else {
            dispatch_main_async_safe(^{
                [self removeActivityIndicator];
                if (completedBlock) {
                    NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                    completedBlock(nil, error, SDImageCacheTypeNone, url);
                }
            });
        }
    }
    
    • 从代码可以看出,代码使用时runtime为分类添加属性,设置placeholder一定是在主线程中进行的,图片真正的下载操作,使用的是SDWebImageOperation。

    UIImageView如何删除缓存

    *Memory内存缓存-----自动删除机制:当系统内存紧张时,NSCache 会自动删除一些缓存对象

    #if SD_UIKIT
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(didReceiveMemoryWarning:)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
    #endif
    
    - (void)didReceiveMemoryWarning:(NSNotification *)notification {
        // Only remove cache, but keep weak cache
        [super removeAllObjects];
    }
    
    - (void)dealloc {
    #if SD_UIKIT
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    #endif
    }
    

    *清空Disk磁盘缓存---------为了消除由“cleanDisk”和“clearDisk”引起的混乱。SDWebImage4.0将“cleanDiskWithCompletion:”更名为“deleteOldFilesWithCompletion:”。

    - (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
        dispatch_async(self.ioQueue, ^{
            [self.diskCache removeExpiredData];
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    }
    
    - (void)removeExpiredData {
        //1、根据时间顺序(7天周期,默认7天)过期的删除
        //2、把之前没删除的按时间顺序存起来,根据自定义的最大缓存(默认最大缓存为磁盘大小),删除到最大缓存的一半为止
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
        for (NSString *fileName in [self.fileManager enumeratorAtPath:self.cachePath]) {
            NSString *filePath = [self.cachePath stringByAppendingPathComponent:fileName];
            NSDate *modificationDate = [[self.fileManager attributesOfItemAtPath:filePath error:nil] objectForKey:NSFileModificationDate];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [self.fileManager removeItemAtPath:filePath error:nil];
            }
        }
    }
    
    • 磁盘清理的时间点---分别在『应用被杀死时』和 『应用进入后台时』进行清理操作
    #if SD_UIKIT
            // Subscribe to app events
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(applicationWillTerminate:)
                                                         name:UIApplicationWillTerminateNotification
                                                       object:nil];
    
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(applicationDidEnterBackground:)
                                                         name:UIApplicationDidEnterBackgroundNotification
                                                       object:nil];
    #endif
    
    #if SD_UIKIT || SD_MAC
    - (void)applicationWillTerminate:(NSNotification *)notification {
        [self deleteOldFilesWithCompletionBlock:nil];
    }
    #endif
    
    #if SD_UIKIT
    - (void)applicationDidEnterBackground:(NSNotification *)notification {
        if (!self.config.shouldRemoveExpiredDataWhenEnterBackground) {
            return;
        }
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
            return;
        }
        UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
        __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
            // Clean up any unfinished task business by marking where you
            // stopped or ending the task outright.
            [application endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];
    
        // Start the long-running task and return immediately.
        [self deleteOldFilesWithCompletionBlock:^{
            [application endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];
    }
    #endif
    

    SDWebImage源码解析思维导图总结如下:

    SDWebImage源码解析.png

    相关文章

      网友评论

          本文标题:SDWebImage深度剖析

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