在ios应用程序上缓存图像的最佳方法?

时间:2012-07-16 19:52:11

标签: ios image uitableview caching load

很快,我有一个NSDictionary的图片网址,我需要在UITableView中显示。每个单元格都有标题和图像。我成功地实现了这一点,虽然滚动是滞后的,因为看起来细胞每次进入屏幕时都会下载图像。 我搜索了一下,在github上找到了SDWebImage。这使得卷轴拉开了。我不完全确定它做了什么,但我相信它做了一些缓存。 但!每当我第一次打开应用程序时,我看到没有图像,我必须向下滚动,然后备份它们才能到达。如果我用主页按钮退出应用程序,然后重新打开,那么它就像缓存一样工作,因为屏幕上的图像是可见的,但是,如果我向下滚动一个单元格,那么下一个单元格没有图像。直到我滚过它并备份,或者我点击它。这是缓存应该如何工作?或者缓存从网上下载的图像的最佳方法是什么?图像正在被更新,所以我接近将它们导入到项目中,但我希望能够在不上传更新的情况下更新图像。

是否无法在启动时从缓存中加载整个tableview的所有图像(假设缓存中有某些内容)?这就是为什么我有时看到没有图像的细胞?

是的,我很难理解缓存是什么。

- 编辑 -

我只使用相同尺寸(500x150)的图像尝试了这个,并且方面错误消失了,但是当我向上或向下滚动时,所有单元格上都有图像,但起初它们是错误的。单元格在视图中显示几毫秒后,将显示正确的图像。这真令人讨厌,但也许它必须如何?它似乎首先从缓存中选择了错误的索引。如果我滚动慢,那么我可以看到图像从错误的图像闪烁到正确的图像。如果我快速滚动,那么我相信错误的图像在任何时候都是可见的,但由于快速滚动我无法分辨。当快速滚动减慢并最终停止时,仍会出现错误的图像,但在停止滚动后立即更新为正确的图像。我也有一个自定义UITableViewCell课程,但我没有做过任何重大修改..我还没有完成我的代码,但我想不出可能出错的地方..也许我有一些错误的顺序..我已经编写了很多java,c#,php等,但我很难理解Objective-c,所有.h.m ...... 我也有`

@interface FirstViewController : UITableViewController{

/**/
NSCache *_imageCache;
}
<{1>}中的

(以及其他变量)。这不正确吗?

这是我的FirstViewController.h

cellForRowAtIndexPath

7 个答案:

答案 0 :(得分:71)

缓存只是意味着保留所需数据的副本,这样您就不必从较慢的源加载它。例如,微处理器通常具有高速缓存存储器,它们保存数据副本,这样它们就不必访问RAM,这要慢得多。硬盘通常具有内存缓存,文件系统可以从中快速访问最近访问过的数据块。

同样,如果您的应用从网络加载了大量图片,则可能需要将它们缓存在您的设备上而不是每次需要时都下载它们。有很多方法可以做到这一点 - 听起来你已经找到了一个。您可能希望将下载的图像存储在应用程序的/ Library / Caches目录中,特别是如果您不希望它们发生更改。从二级存储装载图像将比通过网络加载图像快得多。

您可能也对这个鲜为人知的NSCache类感兴趣,以便在内存中保留您需要的图像。 NSCache就像一本字典,但是当内存变得紧张时,它会开始释放它的一些内容。您可以先检查给定图像的缓存,如果在那里找不到它,则可以查看缓存目录,如果没有找到它,则可以下载它。这些都不会在您第一次运行时加快应用程序上的图像加载速度,但是一旦您的应用程序下载了大部分需要的内容,它就会响应更快。

答案 1 :(得分:23)

我认为Caleb很好地回答了缓存问题。我只是要在您检索图像时触及更新UI的过程,例如假设您的图片NSCache名为_imageCache

首先,为tableview定义一个操作队列属性:

@property (nonatomic, strong) NSOperationQueue *queue;

然后在viewDidLoad中初始化:

self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 4;

然后在cellForRowAtIndexPath中,您可以:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ilvcCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // set the various cell properties

    // now update the cell image

    NSString *imagename = [self imageFilename:indexPath]; // the name of the image being retrieved

    UIImage *image = [_imageCache objectForKey:imagename];

    if (image)
    {
        // if we have an cachedImage sitting in memory already, then use it

        cell.imageView.image = image;
    }
    else
    {
        cell.imageView.image = [UIImage imageNamed:@"blank_cell_image.png"];

        // the get the image in the background

        [self.queue addOperationWithBlock:^{

            // get the UIImage

            UIImage *image = [self getImage:imagename];

            // if we found it, then update UI

            if (image)
            {
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // if the cell is visible, then set the image

                    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                    if (cell)
                        cell.imageView.image = image;
                }];

                [_imageCache setObject:image forKey:imagename];
            }
        }];
    }

    return cell;
}

我只提到这个,因为我最近在SO上看到了一些代码示例,它们使用GCD来更新相应的UIImageView image属性,但是在调度UI的过程中更新回主队列,他们采用了好奇的技术(例如,重新加载单元格或表格,只更新cell顶部返回的现有tableView:cellForRowAtIndexPath对象的图像属性(这是一个问题)如果该行已从屏幕滚动并且该单元格已经出列并且正在重新用于新行),等等。通过使用cellForRowAtIndexPath(不要与tableView:cellForRowAtIndexPath混淆),您可以确定单元格是否仍然可见和/或它是否已滚动并已出列并重新使用。

答案 2 :(得分:13)

最简单的解决方案是使用经过压力测试的大量使用的东西。

SDWebImage是一个功能强大的工具,帮助我解决了类似的问题,并且可以轻松安装wocoa可可。在podfile中:

platform :ios, '6.1'
pod 'SDWebImage', '~>3.6'

设置缓存:

SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image)
{
    // image is not nil if image was found
}];

缓存图片:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

https://github.com/rs/SDWebImage

答案 3 :(得分:1)

我认为使用像DLImageLoader这样的用户会更好。 更多信息 - &gt; https://github.com/AndreyLunevich/DLImageLoader-iOS

[[DLImageLoader sharedInstance] loadImageFromUrl:@"image_url_here"
                                   completed:^(NSError *error, UIImage *image) {
                                        if (error == nil) {
                                            imageView.image = image;
                                        } else {
                                            // if we got an error when load an image
                                        }
                                   }];

答案 4 :(得分:1)

关于错误图像的部分问题,因为细胞的重复使用。重复使用单元格意味着现有的单元格不在视野范围内(例如,当您向下滚动时从顶部出来的单元格是从底部再次返回的单元格。)所以你得到了图片不正确。但是一旦单元格出现,就会执行获取正确图像的代码,并获得正确的图像。

您可以在&#39; prepareForReuse&#39;中使用占位符。细胞的方法。当您需要在启动单元以供重用时重置值时,通常会使用此功能。在此处设置占位符将确保您不会获得任何不正确的图像。

答案 5 :(得分:0)

缓存图像可以像这样简单地完成。

<强> ImageService.m

@implementation ImageService{
    NSCache * Cache;
}

const NSString * imageCacheKeyPrefix = @"Image-";

-(id) init {
    self = [super init];
    if(self) {
        Cache = [[NSCache alloc] init];
    }
    return self;
}

/** 
 * Get Image from cache first and if not then get from server
 * 
 **/

- (void) getImage: (NSString *) key
        imagePath: (NSString *) imagePath
       completion: (void (^)(UIImage * image)) handler
    {
    UIImage * image = [Cache objectForKey: key];

    if( ! image || imagePath == nil || ! [imagePath length])
    {
        image = NOIMAGE;  // Macro (UIImage*) for no image 

        [Cache setObject:image forKey: key];

        dispatch_async(dispatch_get_main_queue(), ^(void){
            handler(image);
        });
    }
    else
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0ul ),^(void){

            UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[imagePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];

            if( !image)
            {
                image = NOIMAGE;
            }

            [Cache setObject:image forKey: key];

            dispatch_async(dispatch_get_main_queue(), ^(void){
                handler(image);
            });
        });
    }
}


- (void) getUserImage: (NSString *) userId
           completion: (void (^)(UIImage * image)) handler
{
    [self getImage: [NSString stringWithFormat: @"%@user-%@", imageCacheKeyPrefix, userId]
         imagePath: [NSString stringWithFormat: @"http://graph.facebook.com/%@/picture?type=square", userId]
        completion: handler];
}

<强> SomeViewController.m

    [imageService getUserImage: userId
                    completion: ^(UIImage *image) {
            annotationImage.image = image;
    }];

答案 6 :(得分:0)

////.h file

#import <UIKit/UIKit.h>

@interface UIImageView (KJ_Imageview_WebCache)


-(void)loadImageUsingUrlString :(NSString *)urlString placeholder :(UIImage *)placeholder_image;

@end

//.m file


#import "UIImageView+KJ_Imageview_WebCache.h"


@implementation UIImageView (KJ_Imageview_WebCache)




-(void)loadImageUsingUrlString :(NSString *)urlString placeholder :(UIImage *)placeholder_image
{




    NSString *imageUrlString = urlString;
    NSURL *url = [NSURL URLWithString:urlString];



    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,     NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *getImagePath = [documentsDirectory stringByAppendingPathComponent:[self tream_char:urlString]];

    NSLog(@"getImagePath--->%@",getImagePath);

    UIImage *customImage = [UIImage imageWithContentsOfFile:getImagePath];




    if (customImage)
    {
        self.image = customImage;


        return;
    }
    else
    {
         self.image=placeholder_image;
    }


    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *uploadTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {


        if (error)
        {
            NSLog(@"%@",[error localizedDescription]);

           self.image=placeholder_image;

            return ;
        }

        dispatch_async(dispatch_get_main_queue(), ^{


            UIImage *imageToCache = [UIImage imageWithData:data];



            if (imageUrlString == urlString)
            {

                self.image = imageToCache;
            }



            [self saveImage:data ImageString:[self tream_char:urlString]];

        });




    }];

    [uploadTask resume];



}


-(NSString *)tream_char :(NSString *)string
{
    NSString *unfilteredString =string;

    NSCharacterSet *notAllowedChars = [[NSCharacterSet characterSetWithCharactersInString:@"!@#$%^&*()_+|abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"] invertedSet];
    NSString *resultString = [[unfilteredString componentsSeparatedByCharactersInSet:notAllowedChars] componentsJoinedByString:@""];
    NSLog (@"Result: %@", resultString);



    return resultString;

}

-(void)saveImage : (NSData *)Imagedata ImageString : (NSString *)imageString
{

    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:imageString];




    if (![Imagedata writeToFile:documentDirectoryFilename atomically:NO])
    {
        NSLog((@"Failed to cache image data to disk"));
    }
    else
    {
        NSLog(@"the cachedImagedPath is %@",documentDirectoryFilename);
    }

}

@end


/// call 

 [cell.ProductImage loadImageUsingUrlString:[[ArrProductList objectAtIndex:indexPath.row] valueForKey:@"product_image"] placeholder:[UIImage imageNamed:@"app_placeholder"]];