SDWebImage源码阅读
2020-05-14 本文已影响0人
廖小凡
序言
SDWebImage作为GitHub上拥有22.2k星星的开源库,是值得我们去阅读它的源码,很多可以学习的东西,一个简单的API:sd_setImageWithURL
,其中就包括了查找缓存数据,下载图片数据,缓存数据,存进磁盘等操作,让我们来看一下是如何实现的吧。
内容
SDWebImageManager
SDWebImageManager
作为一个管理类,提供接口给外部调用,自己在类里实现了一个三级缓存机制。
- 下载数据,
SDWebImageDownloader
实现的。 - 内存缓存,
SDImageCache
实现的。 - 磁盘缓存,
SDImageCache
实现的。
关键代码
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
__weak SDWebImageCombinedOperation *weakOperation = operation;
//查询缓存池里有没有这个数据
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
NSString *key = [self cacheKeyForURL:url];
BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
&& (!cachedImage || options & SDWebImageRefreshCached)
&& (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
if (shouldDownload) {
__weak typeof(strongOperation) weakSubOperation = strongOperation;
//根据url下载数据
strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
//将数据存进缓存池
NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
[self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
}
else {
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
}
}
这里只截了一些关键代码出来,具体SDWebImage针对很多情况都有一些处理,有兴趣的同学可以去深入了解下。
SDWebImageDownloader
downloadImageWithURL
方法是SDWebImageDownloader
的,这个类是负责下载数据的。
关键代码
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
//将request传给SDWebImageDownloaderOperation
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
}
SDWebImageDownloader
里面用了SDWebImageDownloaderOperation
这个类来下载数据,查看这个类能发现是用的NSURLSession来实现下载数据的。
SDImageCache
queryCacheOperationForKey
和storeImage
都是SDImageCache
里面的方法,这个类是负责缓存数据。
关键代码
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
SDImageFormat format;
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
[self _storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
}
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
[self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
NSString *cachePathForKey = [self defaultCachePathForKey:key];
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
从代码里可以看出,内存缓存是用了SDMemoryCache
这个类,继承于NSCache,而磁盘缓存是用writeToURL
写进文件里面。
总结
- 为什么要用NSCache?
NSCache是系统创建用来缓存的对象,有做一些专门的处理,比如,系统资源耗尽,会自动删减缓存,会删减最久未使用的对象,并且是线程安全的。 - 为什么得创建那么多类?
分工明确,哪个对象负责哪部分业务,符合面向对象设计的单一职责原则。 - 缓存机制是怎样的?
流程大概是这样的,SDWebImage拿到url,会先从内存缓存里面找,找到直接返回,找不到就去磁盘缓存里面找,找到返回并添加到内存缓存里面,找不到才去下载,下载后存进磁盘缓存和磁盘缓存。 - 读写磁盘数据如何避免耗时操作阻塞线程?
耗时操作交给子线程执行。 - 如何保证线程安全?
SDWebImage里面是用的GCD的信号量加锁。