如何设计一个图片缓存框架?
可以模仿 SDWebImage
来实现。
- 构成
- Manager
- 内存、磁盘缓存
- 网络下载
- Code Manager
- 图片解码
- 图片解压缩
- 内存设计需要考虑的问题
- 存储的
Size
,因为内存的空间有限,我们针对不同尺寸的图片,给出不同的方案10K
以下的50个100Kb
以下的20个100kb
以上的10个
- 淘汰的策略:内存的淘汰策略 采取
LRU(最近最少使用算法)
- 定期检查(不建议,耗性能)
- 提高检查触发频率(一定要注意开销)
- 前后台切换的时候
- 每次读写的时候
- 存储的
- 磁盘设计需要考虑的问题
- 存储方式
- 大小限制(有固定的大小)
- 移除策略(可以设置为7天或者15天)
- 网络设计需要考虑的问题
- 图片请求的最大并发量
- 请求超时策略
- 请求优先级
- 图片解码
- 应用
策略模式
,针对jpg
、png
、gif
等不同的图片格式进行解码 - 图片解码的时机
- 在 子线程 图片刚下载完时
- 在 子线程 刚从磁盘读取完时
- 避免在主线程解压缩、解码,避免卡顿
- 应用
SDWebImage解读
图片加载流程:
1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
4.下载后的图片会加入缓存中,并写入磁盘中
5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
复制代码
SDWebImage主要有以下类:
SDWebImageDownloader
:图片下载管理类SDImageCache
: 图片缓存管理类SDWebImageManager
:核心类,持有SDWebImageDownloader
和SDImageCache
SDWebImageCombinedOperation
:从缓存加载图片操作类SDWebImageDownloaderOperation
:从服务端下载图片操作类UIImageView+WebCache
: 暴露接口,内部SDWebImageManager
去加载图片
UIImageView+WebCache.m
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
/*
* 取消当前UIImageView的所有图片下载操作(runtime绑定的loadOperationKey字典下,查找key=UIImageViewImageLoad遵守SDWebImageCombinedOperation协议的操作,通过协议取消下载)
*/
[self sd_cancelCurrentImageLoad];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/*----
省略
----*/
if (!url) {
// completedBlock失败
return;
}
__weak __typeof(self)wself = self;
/*
* SDWebImageManager管理器进行图片加载(从内存、磁盘或服务器加载)
* 当内存或磁盘有缓存时,operation是SDWebImageCombinedOperation;当内存或磁盘没有缓存时(服务端加载),operation是SDWebImageDownloaderOperation;
*/
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
dispatch_main_sync_safe(^{
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);
}
});
}];
// 将operation下载操作放入loadOperationKey字典中
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
}
复制代码
SDWebImageManager.m
//////////////////////////////////////////////////
//负责管理cache,涉及内存缓存和硬盘保存
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//负责从网络下载图片
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
//
@property (strong, nonatomic) NSMutableArray <SDWebImageCombinedOperation *>*runningOperations;
//////////////////////////////////////////////////
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// 实例化一个下载操作
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
// 缓存无用的url,避免下次重复下载无用的图片路径
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
// 图片url为空或url是无效的(如404) 就不执行下载,直接return
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}
// 下载操作operation放入到SDWebImageManager下的operation数组中
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];
/*
* 根据key(图片url)尝试从缓存中加载图片
* done回调中SDImageCacheType枚举:是磁盘还是内存中的图片数据
*/
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
// 如果操作已经取消,移出操作元素直接返回
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
// 如果内存或磁盘有图片,则回调缓存中的图片
if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
// 移除操作
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
// 如果内存和磁盘都没有图片,就使用SDWebImageDownloader从服务器去下载
else {
/*----
SDWebImageDownloaderOptions downloaderOptions策略忽略
---*/
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
if (!strongOperation || strongOperation.isCancelled) {
// 加载图片取消,则什么也不做
} else if (error) {
// 加载图片错误回调
}
else {
// 加载成功,存储图片到缓存(内存和磁盘)
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
}
// 主线程回调
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}
if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
}
}];
return operation;
}
复制代码
SDImageCache.m
//////////////////////////////////////////////////////////////////////
// 内存中图片缓存
@property (strong, nonatomic) NSCache *memCache;
// 磁盘读取图片队列
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
//////////////////////////////////////////////////////////////////////
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
/*省略*/
// 首先尝试从内存中图片缓存(NSCache *memCache属性)
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
// 内存中没有,就从磁盘中读取
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
@autoreleasepool {
// 从磁盘中获取图片
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
// 磁盘中有图片,就把图片放到内存cache中
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
// 磁盘中查询到图片回调
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
return operation;
}
复制代码
SDWebImageDownloader.m
//////////////////////////////////////////////////////////////////////
// 存储下载回调,key是图片url,value数组,数组有进度回调、完成下载回调等
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
// 下载队列
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
//////////////////////////////////////////////////////////////////////
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
{
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:0 timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
operation = [[wself.operationClass alloc] initWithRequest:request inSession:self.session options:options progress:nil
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
// 下载完成,回调
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:nil];
/*----
省略
----*/
// operation放到队列中
[wself.downloadQueue addOperation:operation];
}];
return operation;
}
复制代码