iOS 中的永久图像缓存

Posted

技术标签:

【中文标题】iOS 中的永久图像缓存【英文标题】:Permanent Image Caching in iOS 【发布时间】:2012-09-09 01:02:28 【问题描述】:

我正在开发一个应用程序,我需要在其中永久下载和存储图像,直到我手动删除它们(内存中 + 磁盘)。这是必需的,因为应用程序需要能够离线工作。我知道有 AFNetworking 和 SDWebImage 用于缓存图像,但我认为它们不允许永久缓存和手动删除。 Afaik,当缓存过期时,它们会自动删除图像。

还有其他库可以做这种事情吗?我试图为此编写一个类,但它工作得不是很稳定。我想最好不要重新发明***。

【问题讨论】:

有 NSCache 和 NSPurgeableData 但可以满足你“永久”的需求。如果永久可能不是缓存 - 更多的是商店。 developer.apple.com/library/mac/#technotes/… 你为什么不把它们保存到文档目录? 【参考方案1】:

将文件保存在 Application Support 内的文件夹中。这些将持续存在,ios 不会删除它们。我说以后用文件夹,如果你想删除它们,你可以删除文件夹。

具体如何访问和配置Application Support目录详细in this post

如果需要,您可以将这些图像的 URL 或路径保存在 Core Data 存储库中。

编辑:我有一个管理我的核心数据图像存储库的单例类。与图像相关联的对象具有 data 属性、URL 属性、filePath 属性和表示是否存在未完成提取的布尔标志。当某物需要图像时,它会请求图像(由数据构建),如果没有数据,则单例会为其发出 Web 获取。当它到达时,它被存储在文件系统中,并且实体获得该图像集的文件路径。然后它会发出通知,告知某某图像已到达。 [为了调试,我也在不同的点上进行了测试,以确保我可以从数据中创建图像。]

当 viewController 收到通知时,它们会查看可见单元格列表,如果其中任何一个与新到达的图像相关联,则该类会从单例中请求图像,然后将其设置到单元格上。这对我来说效果很好。

【讨论】:

这几乎正是我现在正在做的事情。问题是,异步下载图像,将它们设置为表格单元格等非常痛苦。此外,尽管服务器中的 JPEG 没有损坏,但我经常收到“ImageIO:JPEG Corrupt JPEG 数据:数据段过早结束”错误。这就是为什么我问是否有一个易于使用的库。 好吧,我不会用痛苦这个词,但会说复杂。当你现在收到数据时,最后一点,你得到 connectionDidFinishLoading,然后(作为一种调试技术)尝试看看你是否可以创建一个图像 'UIImage *img = [UIImage alloc] initWithData:data]' - 如果你经常失败,那么您的数据处理就有问题 - 如果它从未失败但您稍后会失败,那么您的存储/检索失败了。【参考方案2】:

遗憾的是 SDWebImage 没有提供这样的能力 因此,为了利用 SDWebImage 提供的高级缓存功能,我围绕 SDWebImage 编写了一个包装器

基本上这个类管理一个备用永久缓存,因此如果在 SDWeb 图像缓存“磁盘和内存”中找不到请求的图像,它将在永久缓存中查找它

使用这种方法,我设法使用 SDWebImage 像往常一样将图像设置为表格单元格

.h 文件

@interface CacheManager : NSObject
+ (CacheManager*)sharedManager;

// Images
- (BOOL) diskImageExistsForURL:(NSURL*) url;
- (void) downloadImage:(NSURL*) url completed:(void(^)(UIImage*))onComplete;
- (void) setImage:(NSURL*)url toImageView:(UIImageView*)iv completed:(void(^)(UIImage*))onComplete;
- (void) clearImageCache;

@end

.m 文件

#import "CacheManager.h"
#import <SDWebImage/UIImageView+WebCache.h>


#define CACH_IMAGES_FOLDER      @"ImagesData"


@implementation CacheManager


static CacheManager *sharedManager = nil;

#pragma mark -
#pragma mark Singilton Init Methods
// init shared Cache singleton.
+ (CacheManager*)sharedManager
    @synchronized(self)
        if ( !sharedManager )
            sharedManager = [[CacheManager alloc] init];
        
    
    return sharedManager;


// Dealloc shared API singleton.
+ (id)alloc
    @synchronized( self )
        NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton.");
        return [super alloc];
    
    return nil;


// Init the manager
- (id)init
    if ( self = [super init] )
    return self;


/**
  @returns YES if image found in the permanent cache or the cache managed by SDWebImage lib
 */
- (BOOL) diskImageExistsForURL:(NSURL*) url

    // look for image in the SDWebImage cache
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
    if([manager diskImageExistsForURL:url])
        return YES;

    // look for image in the permanent cache
    NSString *stringPath = url.path;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    return [fileManager fileExistsAtPath:stringPath];


/**
 get the image with specified remote url asynchronosly 
 first looks for the image in SDWeb cache to make use of the disk and memory cache provided by SDWebImage
 if not found, looks for it in the permanent cache we are managing, finally if not found in either places
 it will download it using SDWebImage and cache it.
 */
- (void) downloadImage:(NSURL*) url completed:(void(^)(UIImage*))onComplete

    NSString *localPath = [[self getLocalUrlForImageUrl:url] path];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    // -1 look for image in SDWeb cache
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
    if([manager diskImageExistsForURL:url])
        [manager downloadImageWithURL:url options:SDWebImageRetryFailed
                             progress:^(NSInteger receivedSize, NSInteger expectedSize) 
                            completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) 
                                onComplete(image);

                                // save the image to the perminant cache for later
                                // if not saved before
                                if(image)
                                    if ([fileManager fileExistsAtPath:localPath])
                                        NSURL* localeUrl = [self getLocalUrlForImageUrl:url];
                                        [self saveImage:image toCacheWithLocalPath:localeUrl];
                                    
                                
                            ];
        return;
    

    // -2 look for the image in the permanent cache
    if ([fileManager fileExistsAtPath:localPath])
        UIImage *img = [self getImageFromCache:url];
        onComplete(img);
        // save image back to the SDWeb image cache to make use of the memory cache
        // provided by SDWebImage in later requests
        [manager saveImageToCache:img forURL:url];
        return;
    

    // -3 download the image using SDWebImage lib
    [manager downloadImageWithURL:url options:SDWebImageRetryFailed
                         progress:^(NSInteger receivedSize, NSInteger expectedSize) 
                        completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) 
                            onComplete(image);
                            // save the image to the permanent cache for later
                            if(image)
                                NSURL* localeUrl = [self getLocalUrlForImageUrl:url];
                                [self saveImage:image toCacheWithLocalPath:localeUrl];
                            
                        ];



- (void) setImage:(NSURL*)url toImageView:(UIImageView*)iv completed:(void(^)(UIImage*))onComplete
    [self downloadImage:url completed:^(UIImage * downloadedImage) 
        iv.image = downloadedImage;
        onComplete(downloadedImage);
    ];



/**
 @param:imgUrl : local url of image to read
 */
- (UIImage*) getImageFromCache:(NSURL*)imgUrl
    return [UIImage imageWithData: [NSData dataWithContentsOfURL:imgUrl]];


/**
 writes the suplied image to the local path provided
 */
-(void) saveImage:(UIImage*)img toCacheWithLocalPath:(NSURL*)localPath
    NSData * binaryImageData = UIImagePNGRepresentation(img);
    [binaryImageData writeToFile:[localPath path] atomically:YES];


// Generate local image URL baesd on the name of the remote image
// this assumes the remote images already has unique names
- (NSURL*)getLocalUrlForImageUrl:(NSURL*)imgUrl
    // Saving an offline copy of the data.
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesDirectory = [paths objectAtIndex:0];
    NSString *folderPath = [cachesDirectory stringByAppendingPathComponent:CACH_IMAGES_FOLDER];
    BOOL isDir;
    // create folder not exist
    if (![fileManager fileExistsAtPath:folderPath isDirectory:&isDir])
        NSError *dirWriteError = nil;
        if (![fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&dirWriteError])
            NSLog(@"Error: failed to create folder!");
        
    

    NSString *imgName = [[[imgUrl path] lastPathComponent] stringByDeletingPathExtension];

    NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
    NSString *pathString = [NSString stringWithFormat:@"%@/%@", CACH_IMAGES_FOLDER, imgName];
    return [cachesDirectoryURL URLByAppendingPathComponent:pathString];



/**
 removes the folder contating the cahced images,
 the folder will be reacreated whenever a new image is being saved to the permanent cache
 */
-(void)clearImageCache
    // set the directory path
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesDirectory = [paths objectAtIndex:0];
    NSString *folderPath =  [cachesDirectory stringByAppendingPathComponent:CACH_IMAGES_FOLDER];
    BOOL isDir;
    NSError *dirError = nil;
    // folder exist
    if ([fileManager fileExistsAtPath:folderPath isDirectory:&isDir])
        if (![fileManager removeItemAtPath:folderPath error:&dirError])
        NSLog(@"Failed to remove folder");
    


@end

【讨论】:

以上是关于iOS 中的永久图像缓存的主要内容,如果未能解决你的问题,请参考以下文章

核心数据和图像缓存

HTTP静态资源缓存永远策略

使用 Glide 将图片永久保存在缓存中

iOS - 优化。缓存图像

在 xib 中分配的图像是不是缓存?

IOS 上的图像缓存