iOS 中的永久图像缓存

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

还有其他库可以做到这种事吗?我尝试编写了一个类来处理此问题,但它的稳定性不太好。我想最好不要重复造轮子。


有NSCache和NSPurgeableData,但可能无法满足您的“永久”需求。如果永久存储可能不是缓存 - 更多的是存储。http://developer.apple.com/library/mac/#technotes/CachingPurgeableMemory/Introduction/Introduction.html - bryanmac
为什么不直接将它们保存到文档目录中呢? - Oscar Gomez
2个回答

0

很遗憾,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

0

将文件保存在Application Support文件夹内。这些文件将持久存在,iOS不会删除它们。我建议使用文件夹,因为以后如果您想要删除它们,只需删除该文件夹即可。

如何访问和配置Application Support目录的详细信息,请参见此文章

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

编辑:我有一个单例类来管理我的Core Data图像存储库。具有与其关联的图像的对象具有数据属性、URL属性、filePath属性和一个布尔标志,指示是否存在未完成的获取。当某个东西需要图像时,它会请求图像(从数据构建),如果没有数据,则单例会发出网络获取。当它到达时,它会被存储在文件系统上,并且实体会获得设置为该图像的filePath。然后发送通知,说明已经到达了某个图像。[为了调试,在各个点上我也测试了一下,以确保我可以从数据中创建图像。]

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


这几乎就是我现在正在做的事情。问题是,异步下载图像,将它们设置为表格单元格等操作非常麻烦。此外,尽管服务器上的JPEG文件没有损坏,但我经常会遇到“ImageIO:JPEG Corrupt JPEG data:premature end of data segment”错误。这就是为什么我问是否有易于使用的库的原因。 - basar
我不会用“痛苦”这个词,但会说它很复杂。当您现在收到数据时,最后一位,并且您收到connectionDidFinishLoading,那么(作为调试技术)请尝试并查看是否可以创建一个图像'UIImage *img = [UIImage alloc] initWithData:data]' - 如果您经常失败,则说明您的数据处理有问题 - 如果它从未失败,但稍后仍然出现故障,则是您的存储/检索出现了问题。 - David H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接