2012-07-16 121 views
39

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

是否无法加载全部图像tableview在启动时形成缓存(假设缓存中有东西)?这就是为什么我有时会看到没有图像的细胞?

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

- 编辑 -

我试图与相同尺寸(500x150)的仅适用于图像,纵横错走了,当我向上或向下滚动,那里都是图像细胞,但一开始他们错了。单元格在视图中停留了几毫秒后,出现右图。这太令人吃惊了,但也许它是怎么回事?它看起来像它首先从缓存中选择了错误的索引。如果我滚动缓慢,那么我可以看到图像从错误的图像闪烁到正确的图像。如果我滚动得很快,那么我相信任何时候都会看到错误的图像,但由于快速滚动,我无法判断。当快速滚动变慢并最终停止时,错误的图像仍然出现,但在滚动停止后立即更新为正确的图像。我也有一个自定义的UITableViewCell类,但我没有做任何大的改变..我还没有经过我的代码非常多,但我想不出什么可能是错误的。也许我有东西在错误的命令。我已经设定用Java,C#,PHP等不多,但我有一个很难理解的Objective-C,与所有的.h.m ... 我也有`

@interface FirstViewController : UITableViewController{ 

/**/ 
NSCache *_imageCache; 
} 

(其他变量)FirstViewController.h。这是不正确的?

这是我的cellForRowAtIndexPath

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

if (cell == nil) 
{ 
    cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 
} 

NSMutableArray *marr = [hallo objectAtIndex:indexPath.section]; 
NSDictionary *dict = [marr objectAtIndex:indexPath.row]; 

NSString* imageName = [dict objectForKey:@"Image"]; 
//NSLog(@"url: %@", imageURL); 

UIImage *image = [_imageCache objectForKey:imageName]; 

if(image) 
{ 
    cell.imageView.image = image; 
} 
else 
{ 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

     NSString* imageURLString = [NSString stringWithFormat:@"example.com/%@", imageName]; 
     NSURL *imageURL = [NSURL URLWithString:imageURLString]; 
     UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:imageURL]]; 

     if(image) 
     { 
      dispatch_async(dispatch_get_main_queue(), ^{ 
       CustomCell *cell =(CustomCell*)[self.tableView cellForRowAtIndexPath:indexPath]; 
       if(cell) 
       { 
        cell.imageView.image = image; 
       } 
      }); 
      [_imageCache setObject:image forKey:imageName]; 
     } 
    }); 
} 

cell.textLabel.text = [dict objectForKey:@"Name"]; 

return cell; 
} 
+1

您可以将代码发布到'cellRowRowAtIndexPath'和其他任何可能与此相关的代码中吗?高速缓存只是存储您希望再次访问的内容,以便在更容易到达的地方加速后续访问。在这种情况下,缓存意味着您的应用程序应该在第一次下载它时实际存储每个单元的图像数据,然后在需要显示特定单元时随时重新使用它(这是SDWebImage应该做的工作)您)。 – Dima 2012-07-16 19:56:05

+0

完成。我已经切换出SDWebImage,但仍然得到一些奇怪的结果..有时在滚动时显示错误的图像。 – Sti 2012-07-17 04:46:07

+1

刚刚在第一个'dispatch_async'之前初始化'cell.imageView.image'。 – Rob 2012-07-17 12:42:12

回答

60

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

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

您可能还对名不见经传的NSCache类感兴趣,该类用于保存内存中所需的图像。 NSCache就像字典一样工作,但是当内存变得紧张的时候,它会开始释放一些内容。你可以首先检查给定图像的缓存,如果你没有找到它,那么你可以查看你的缓存目录,如果你没有找到它,你可以下载它。这些都不会加速您第一次运行应用程序时的图像加载速度,但是一旦您的应用程序下载了大部分所需内容,它就会更加快速响应。

+1

哇,很好的答案。我会检查出NSCache。但我无法想象它是如何工作的。如果我创建一个NSCache对象,让我们说viewDidLoad,每次启动我的应用程序时它不会是空的吗?或者它可能以其他方式工作也许..我如何访问我以前的缓存对象..?那么,我会在早上阅读它。谢谢! – Sti 2012-07-17 01:20:39

+1

是的,会的。然而,阅读更多内容,你会发现它有一个委托方法,它在删除一个对象之前被调用 - 你可以用它来保存对象到辅助存储。或者你可以实现你自己的基于磁盘的缓存,并使NSCache避开等式。 – Caleb 2012-07-17 01:37:27

+0

嗨卡莱布,很好的回答,但我有一个笔记。 RAM访问如何比硬盘访问慢?据我所知,情况恰恰相反,RAM地址访问速度比任何其他内存存储方式都快。谢谢。 – Malloc 2013-06-06 23:12:26

18

我觉得迦勒很好地回答了缓存问题。我将在检索图像时介绍更新用户界面的过程,例如假设你已经为你的图像NSCache称为_imageCache

首先,定义了实现代码如下操作队列属性:

@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更新相应的属性,但在将UI更新分派回主队列的过程中,他们采用了好奇的技术(例如,重新加载单元格或表格,只更新在tableView:cellForRowAtIndexPath顶部返回的现有对象cell的图像属性(如果该行已滚动出屏幕并且该单元已出列并正在重用一个新的行)等)。通过使用cellForRowAtIndexPath(不要与tableView:cellForRowAtIndexPath混淆),您可以确定该单元格是否仍然可见并且/或者它是否已滚动并已被出列并重用。

+0

非常感谢你的回答和代码! 我尝试了代码,但发生了一些奇怪的事情。当我滚动的速度很快时,某些图像上的某些方面是错误的。它像下面图像的外观被转移到其上的图像或其他绕路..这是可以避免的,还是我必须使所有的图像具有相同的大小?(或至少相同的方面).. – Sti 2012-07-17 02:09:14

+0

@StianFlatby这很奇怪。当您滚动较慢而滚动较快时,您会看到不同的行为?你是否将dispatch_async之外的imageview配置为主队列?我做了很多测试,但我不得不承认,我的桌面视图的所有图像都是一致的大小(以及针对桌面视图进行了优化的小缩略图图像...桌面视图中的大图像总是会有可怕的表现)。也许你可以用你的'cellForRowAtIndexPath'的完整源代码更新你的问题。 – Rob 2012-07-17 03:03:52

+0

@StianFlatby我刚刚对混合大小的图像库进行了测试,对我来说,似乎(a)单元格呈现的差异是图像是否从缓存中加载(同步在主队列上)或不是(异步分发来自后台队列),而不是滚动的速度;和(b)它看起来像是如果你同步执行,默认单元格根据图像的大小/出现重新格式化,所以,你要么创建自己的自定义单元格,因此你可以以任何方式配置你的imageView想要将图像调整为一致的大小。 – Rob 2012-07-17 03:34:27

1

我觉得DLImageLoader会给用户带来更好的效果。 更多信息 - >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 
             } 
            }]; 
11

最简单的办法是去的东西大量使用已经压力测试。

SDWebImage是一个功能强大的工具,帮助我解决了类似的问题,并且可以很容易地安装到可可豆荚中。在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

0

缓存图像可以简单地为这个来完成。

ImageService。米

@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; 
    }]; 
0

有关错误图像问题的一部分,这是因为电池的再利用。单元格的重用意味着现有的单元格不再出现在视图中(例如,当您向底部滚动时从顶部的屏幕出来的单元格是从底部再次返回的单元格)。图片不正确。但是一旦单元显示出来,用于获取正确图像的代码就会执行,并获得正确的图像。

您可以在单元格的“prepareForReuse”方法中使用占位符。此功能主要用于当单元格重新启用时需要重置值时。在此处设置占位符将确保您不会看到任何不正确的图像。

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:@"[email protected]#$%^&*()_+|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"]]; 
相关问题