高效图片缓存机制设计思路
- 内存缓存:使用
NSCache
类,它是线程安全的,并且会在系统内存警告时自动释放内存。NSCache
类似NSDictionary
,可以用来存储图片对象,以图片的URL作为键值。
- 磁盘缓存:使用文件系统来存储图片。可以计算图片URL的哈希值作为文件名,将图片数据写入磁盘文件。为了管理磁盘空间,需要设置一定的清理策略,例如定期删除长时间未使用的图片,或者当磁盘占用超过一定阈值时进行清理。
在Objective - C中的实现
- 内存缓存实现
#import <Foundation/Foundation.h>
@interface ImageCache : NSObject
@property (nonatomic, strong) NSCache *memoryCache;
+ (instancetype)sharedCache;
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (void)storeImage:(UIImage *)image inMemoryCacheForKey:(NSString *)key;
@end
@implementation ImageCache
+ (instancetype)sharedCache {
static ImageCache *sharedCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedCache = [[ImageCache alloc] init];
sharedCache.memoryCache = [[NSCache alloc] init];
});
return sharedCache;
}
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memoryCache objectForKey:key];
}
- (void)storeImage:(UIImage *)image inMemoryCacheForKey:(NSString *)key {
if (image && key) {
[self.memoryCache setObject:image forKey:key];
}
}
@end
- 磁盘缓存实现
#import <Foundation/Foundation.h>
@interface DiskCache : NSObject
+ (instancetype)sharedCache;
- (void)storeImageData:(NSData *)imageData forKey:(NSString *)key;
- (NSData *)imageDataFromDiskCacheForKey:(NSString *)key;
- (void)cleanDiskCache;
@end
@implementation DiskCache
+ (instancetype)sharedCache {
static DiskCache *sharedCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedCache = [[DiskCache alloc] init];
});
return sharedCache;
}
- (NSString *)cachePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDir = paths[0];
return [cacheDir stringByAppendingPathComponent:@"ImageCache"];
}
- (void)createCacheDirectoryIfNeeded {
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:[self cachePath]]) {
NSError *error = nil;
[fileManager createDirectoryAtPath:[self cachePath] withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
NSLog(@"Error creating cache directory: %@", error);
}
}
}
- (void)storeImageData:(NSData *)imageData forKey:(NSString *)key {
if (imageData && key) {
[self createCacheDirectoryIfNeeded];
NSString *fileName = [self cachePathForKey:key];
NSError *error = nil;
[imageData writeToFile:fileName options:NSDataWritingAtomic error:&error];
if (error) {
NSLog(@"Error writing image data to disk: %@", error);
}
}
}
- (NSData *)imageDataFromDiskCacheForKey:(NSString *)key {
NSString *fileName = [self cachePathForKey:key];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:fileName]) {
NSError *error = nil;
NSData *imageData = [NSData dataWithContentsOfFile:fileName options:NSDataReadingMappedIfSafe error:&error];
if (error) {
NSLog(@"Error reading image data from disk: %@", error);
}
return imageData;
}
return nil;
}
- (NSString *)cachePathForKey:(NSString *)key {
NSString *fileName = [key stringByAppendingPathExtension:@"png"];
return [[self cachePath] stringByAppendingPathComponent:fileName];
}
- (void)cleanDiskCache {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:[self cachePath] error:nil];
for (NSString *fileName in cacheFiles) {
NSString *filePath = [[self cachePath] stringByAppendingPathComponent:fileName];
NSError *error = nil;
[fileManager removeItemAtPath:filePath error:&error];
if (error) {
NSLog(@"Error removing file from disk cache: %@", error);
}
}
}
@end
- 综合使用
#import "ImageCache.h"
#import "DiskCache.h"
#import <UIKit/UIKit.h>
@interface ImageLoader : NSObject
+ (void)loadImageWithURL:(NSURL *)url completion:(void (^)(UIImage *image))completion;
@end
@implementation ImageLoader
+ (void)loadImageWithURL:(NSURL *)url completion:(void (^)(UIImage *image))completion {
NSString *key = url.absoluteString;
UIImage *image = [[ImageCache sharedCache] imageFromMemoryCacheForKey:key];
if (image) {
completion(image);
return;
}
NSData *imageData = [[DiskCache sharedCache] imageDataFromDiskCacheForKey:key];
if (imageData) {
image = [UIImage imageWithData:imageData];
[[ImageCache sharedCache] storeImage:image inMemoryCacheForKey:key];
completion(image);
return;
}
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data &&!error) {
image = [UIImage imageWithData:data];
[[ImageCache sharedCache] storeImage:image inMemoryCacheForKey:key];
[[DiskCache sharedCache] storeImageData:data forKey:key];
}
dispatch_async(dispatch_get_main_queue(), ^{
completion(image);
});
}];
[task resume];
}
@end
性能问题和内存管理细节
- 性能问题
- 缓存命中率:合理设计缓存键值,确保相同图片不会重复加载。可以对图片URL进行标准化处理,避免因URL参数顺序等差异导致缓存未命中。
- 磁盘I/O性能:尽量减少磁盘读写操作。批量处理磁盘清理等操作,避免在主线程进行磁盘I/O。
- 内存管理细节
- 内存缓存:
NSCache
会自动释放内存,但也要注意缓存对象的生命周期。避免缓存对象持有其他大量内存对象导致内存无法及时释放。
- 磁盘缓存:及时清理磁盘缓存,防止磁盘空间占用过多。可以在应用启动、进入后台等时机进行磁盘缓存清理。同时,在写入磁盘数据时,要注意处理文件系统相关的错误。