iOS三方框架之 - SDWebImage解析

juejin 2019-08-14 16:26:57 136
本文来自 聪莞 ,作者 juejin

对SDWebImage进行解耦

通过SDWebImageManager来进行管理,主要模块有三个:加载模块、缓存模块、下载模块。

image.png

SDWebImage加载图片的流程

先放一张加载流程图:

image.png
UIView+WebCache.m中的sd_internalSetImageWithURL:placeholderImage:options:operationKey:setImageBlock:progress:completed 开始分析

  1. 判断当前view有没有任务进行,每一个view只能对应一个任务,有一个全局的
typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;

用来存储着每一个view对应的下载operation

    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

//取消对应的Operation
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    SDOperationsDictionary *operationDictionary = [self operationDictionary];
    id operations = operationDictionary[key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}
复制代码
  1. 开始调用loadImageWithUrl:
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            [sself sd_removeActivityIndicator];
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    //下一个runloop刷新周期对当前view进行刷新
                    [sself sd_setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
复制代码

这一步就会根据上面那张流程图那样:先去查找缓存——>再去进行下载——>分两步(UI显示和图片缓存),这里UI显示的时候会根据SDWebImageOptions来决定是否需要对图片进行一些处理,比如:

    /**
     * 只进行内存缓存,不进行磁盘缓存
     */
    SDWebImageCacheMemoryOnly = 1 << 2,

    /**
     * 这个标志可以渐进式下载,显示的图像是逐步在下载(就像你用浏览器浏览网页的时候那种图片下载,一截一截的显示
     */
    SDWebImageProgressiveDownload = 1 << 3,
    /**
     * 在加载图片时加载占位图。 此标志将延迟加载占位符图像,直到图像完成加载。
     */
    SDWebImageDelayPlaceholder = 1 << 9,
    /**
     * 图片在下载后被加载到imageView。但是在一些情况下,我们想要设置一下图片(引用一个滤镜或者加入透入动画)
     * 使用这个来手动的设置图片在下载图片成功后
     */
    SDWebImageAvoidAutoSetImage = 1 << 11,
复制代码

等等,这里没有列举完全,可以自己看源码枚举里的注释。

SDWebImage缓存模块

SDWebImage使用的是内存和磁盘双缓存。 两个类:SDImageCacheConfig:对当前缓存的配置SDImageCache:具体的缓存查找

SDImageCacheConfig中有一些参数,比如:

是否要压缩图片,默认YES: @property (assign, nonatomic) BOOL shouldDecompressImages; 压缩图片可以提高性能,但会消耗内存

是否需要内存缓存,默认YES @property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

还有一些属性也不一一列举了,可以自行查看源码。

SDImageCache里包含了两个属性:

//内存缓存
@property (strong, nonatomic, nonnull) NSCache *memCache;
//磁盘缓存
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
复制代码
  • 内存缓存 SDWebImage提供了一个继承于NSCache的缓存类AutoPurgeCache,这个类注册了一个内存警告的通知:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];

在收到内存警告的时候,进行缓存清理。

  • 磁盘缓存
  1. 生成一个文件目录:
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
复制代码
  1. SDWebImage为每一个缓存文件生成了一个md5的文件名,放到文件目录当中。

具体读取图片时的源码:

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // 检查内存缓存
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            //检查磁盘缓存
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                //磁盘缓存找到后缓存到内存
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}
复制代码

SDWebImage下载模块

同样是两个类SDWebImageDownloaderSDWebImageDownloaderOperation SDWebImageDownloader管理类:负责处理一些公共的信息,比如下载的相关参数、下载队列里的先后顺序、最大下载任务数量、请求头、参数的设置等等

//优先级枚举:
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
    //先进先出  默认
    SDWebImageDownloaderFIFOExecutionOrder,

    //后进先出
   SDWebImageDownloaderLIFOExecutionOrder
};
//设置优先级 后进先出
  if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) 
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }
复制代码

SDWebImageDownloaderOperation:自定义NSOperation来控制其生命周期

  1. 重写了start方法去开始下载任务:[self.dataTask resume];
  2. 下载完成后的回调处理