SDWebImage源码分析

2020-12-28  本文已影响0人  Jey

4个模块

截屏2020-12-28 下午2.46.50.png

工作流程


截屏2020-12-28 上午10.50.44.png
关键类

SDWebImageManager的核心,负责发起下载和缓存。
SDImageCeche,缓存对象,负责内存和沙盒的缓存。
SDWebImageDownloader负责所有图片的下载操作。
SDWebImageDownloaderOperation负责单个文件的下载。

SDWebImageManager图片加载的流程
-downloadImageWithURL: options: progress: completed:

该会先将传入的 progressBlock 和 completedBlock 保存起来,并在第一次下载该 URL 的图片时,创建一个 NSMutableURLRequest 对象和一个 SDWebImageDownloaderOperation 对象,并将该 SDWebImageDownloaderOperation 对象添加到 SDWebImageDownloader 的downloadQueue 来启动异步下载任务,使用了NSOperationQueue这种多线程,更加对象化,可取消图片的下载操作。

SDImageCeche清理缓存
  1. 初始化NSCache的时候,注册了通知,收到了内存警告就清理,最大存储时间是7天,到了也清理。
  2. 分为clearMemory,clearDisk.
  3. 清理磁盘流程,先找到磁盘目录,遍历创建文件目录的枚举器,找到过期的文件,添加到一个数组中,没有过期的文件,以键值形式存到一个字典中。
  4. 遍历删除过期的文件。
  5. 再检测磁盘的缓存大小,超过最大缓存值,把上一步缓存的文件根据修改时间排序,遍历删除,直到最大缓存大小的一半(时间最早的最先删除)。
SDWebImageDownloader下载

最大并发数,在SDWebImageDownloaderConfig中默认了

        _maxConcurrentDownloads = 6;
        _downloadTimeout = 15.0;

关键代码分析

  1. UIKit调用sd的时候都会走到UIView+WebCache中的,为什么都走这里呢,方便统一管理取消任务
- (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;

第一步:判断这个View上有没有在执行任务,经常出现在UITableview中

if (!validOperationKey) {
        // pass through the operation key to downstream, which can used for tracing operation or image view class
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    self.sd_latestOperationKey = validOperationKey;
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];

如果有,取消当前任务,通过当前view的key,这个可以值存在UIView+WebCacheOperation中

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        // Cancel in progress downloader from queue
        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;
    }
}

添加operation,在

- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
    if (key) {
        [self sd_cancelImageLoadOperationWithKey:key];
        if (operation) {
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            @synchronized (self) {
                [operationDictionary setObject:operation forKey:key];
            }
        }
    }
}

这个过程保证了一个UIImageView只会添加一次任务。
id <SDWebImageOperation> operation类型,它是一个

  1. 接下来就是初始化SDWebImageManager,还有一些进度的block

  2. 进入管理类的loadImageWithURL方法,有缓存就加载缓存,没缓存就网络获取。在completed回调中可以看出SDImageCacheType

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) {

这一步主要看SDWebImageCache类,怎么获取缓存图片,存储图片,管理磁盘内存。清理缓存看上面介绍的SDImageCeche清理缓存

  1. 内存缓存的具体操作,内存缓存中SDMemoryCache有一段存取的代码
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
    [super setObject:obj forKey:key cost:g];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key && obj) {
        // Store weak cache
        SD_LOCK(_weakCacheLock);
        [self.weakCache setObject:obj forKey:key];
        SD_UNLOCK(_weakCacheLock);
    }
}

- (id)objectForKey:(id)key {
    id obj = [super objectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return obj;
    }
    if (key && !obj) {
        // Check weak cache
        SD_LOCK(_weakCacheLock);
        obj = [self.weakCache objectForKey:key];
        SD_UNLOCK(_weakCacheLock);
        if (obj) {
            // Sync cache
            NSUInteger cost = 0;
            if ([obj isKindOfClass:[UIImage class]]) {
                cost = [(UIImage *)obj sd_memoryCost];
            }
            [super setObject:obj forKey:key cost:cost];
        }
    }
    return obj;
}

SDMemoryCache重写了NSCache的存取方法
问题:

  1. SDMemoryCache继承系统的NSCache
  2. 但是发现缓存中使用的self.weakCache是NSMapTable,而且存储的时候调用了父类的set方法 [super setObject:obj forKey:key cost:g];
  3. 这样内存缓存中就有两份缓存了,这是为什么?
  4. 系统缓存可能随时被清理,所以存储的时候存储两份,一份系统,一份自己维护
  5. 先去系统NSCache中去,没有就在self.weakCache中找,找到了再次[super setObject:obj forKey:key cost:cost];
  6. 提高了查找速度
  1. SDImageCeche缓存中使用GCD的异步串行解码、计算尺寸大小、删除文件操作
dispatch_async(self.ioQueue, ^{
        @autoreleasepool {
            NSData *data = imageData;
            if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
                // If image is custom animated image class, prefer its original animated data
                data = [((id<SDAnimatedImage>)image) animatedImageData];
            }
            if (!data && image) {
                // Check image's associated image format, may return .undefined
                SDImageFormat format = image.sd_imageFormat;
                if (format == SDImageFormatUndefined) {
                    // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
                    if (image.sd_isAnimated) {
                        format = SDImageFormatGIF;
                    } else {
                        // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
                        format = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG;
                    }
                }
                data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
            }
            [self _storeImageDataToDisk:data forKey:key];
            [self _archivedDataWithImage:image forKey:key];
        }
        
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
上一篇 下一篇

猜你喜欢

热点阅读