在iOS应用上caching图像的最佳方法?
不久,我有一个NSDictionary
与图像的url,我需要在我的UITableView
显示。 每个单元格都有一个标题和一个图像。 我已经成功地做到了这一点,虽然滚动是滞后的,因为它似乎像细胞下载他们的图像,每次他们进入屏幕。 我search了一下,在github上find了SDWebImage
。 这使得滚动条消失。 我不完全确定它做了什么,但我相信它做了一些caching。 但! 每次我第一次打开应用程序时,我都看不到图像,我必须向下滚动,然后备份才能到达。 如果我用主页button退出应用程序,并再次打开,那么它看起来像caching工作,因为屏幕上的图像是可见的,但是,如果我向下滚动一个单元格,则下一个单元格没有图像。 直到我滚过去,然后回来,或者如果我点击它。 这是caching应该如何工作? 或者什么是caching从网上下载的图像的最佳方式? 图像正在被更新,所以我只是将它们导入到项目中,但我希望有可能在不上载更新的情况下更新图像。
是否不可能在启动时从caching中整个tableview加载所有图像(假设caching中有东西)? 这就是为什么我有时看到没有图像的细胞?
是的,我很难理解什么是caching。
– 编辑 –
我只用相同尺寸(500×150)的图片进行了尝试,并且纵横误差消失了,但是当我向上或向下滚动时,所有单元格上都有图像,但起初它们是错误的。 在单元格在视图中几毫秒之后,出现正确的图像。 这是令人惊讶的,但也许它是如何呢?它似乎喜欢它首先从caching中select错误的索引。 如果我滚动缓慢,那么我可以看到图像从错误的图像闪烁到正确的。 如果我滚动的速度很快,那么我相信在任何时候都会看到错误的图像,但是由于快速滚动,我无法判断。 当快速滚动变慢并最终停止时,错误的图像仍然出现,但是在停止滚动之后立即更新到正确的图像。 我也有一个自定义的UITableViewCell
类,但我没有做任何大的改变..我还没有经过我的代码非常多,但我想不出什么可能是错的。也许我有东西在错误的顺序..我编程了很多在Java,C#,PHP等,但我很难理解Objective-C,所有的.h
和.m
…我也`
@interface FirstViewController : UITableViewController{ /**/ NSCache *_imageCache; }
(其他variables)在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; }
caching意味着保留您需要的数据的副本,以便您不必从较慢的源加载数据。 例如,微处理器通常在高速caching存储器中保存数据副本,这样他们就不必访问RAM,速度慢得多。 硬盘通常具有内存caching,从中可以使文件系统更快地访问最近访问过的数据块。
同样,如果您的应用程序从networking中加载大量图像,则可能会将其caching在您的设备上,而不是每次需要时都下载它们。 有很多方法可以做到这一点 – 听起来你已经find了一个。 您可能需要将所下载的图像存储在应用程序/ Library / Caches目录中,特别是如果您不希望它们发生更改。 从辅助存储装载图像比在networking上装载图像要快得多。
你可能也会对这个鲜为人知的NSCache类感兴趣,以便将你需要的图像保存在内存中。 NSCache就像字典一样工作,但是当内存变得紧张的时候,它会开始释放一些内容。 你可以先检查给定图像的caching,如果你没有find它,那么你可以看看你的caching目录,如果你没有find它,那么你可以下载它。 这些都不会加速您第一次运行应用程序时的图片加载,但是一旦您的应用程序下载了大部分所需内容,它的响应就会更快。
我想迦勒回答了这个caching问题。 我只是想了解在检索图像时更新UI的过程,例如,假设您的图像具有名为_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更新分派回主队列的过程中,他们采用了好奇的技术(例如,重新加载单元格或表格,只是更新在tableView:cellForRowAtIndexPath
顶部返回的现有cell
对象的图像属性(这是一个问题,如果该行已经滚动屏幕,单元格已被出列,并被重用一个新的行)等)。 通过使用cellForRowAtIndexPath
(不要与tableView:cellForRowAtIndexPath
混淆),您可以确定单元格是否仍然可见和/或是否可能已滚动并已被出列和重用。
最简单的解决scheme是使用经过压力testing的大量使用的东西。
SDWebImage是一个强大的工具,帮助我解决了类似的问题,可以很容易地安装瓦特/cocoa豆荚。 在podfile中:
platform :ios, '6.1' pod 'SDWebImage', '~>3.6'
设置caching:
SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"]; [imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) { // image is not nil if image was found }];
caching图像:
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
我想对你来说会比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 } }];
caching图像可以像这样简单地完成。
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; }];
对于关于错误图像的部分问题,这是因为细胞的重用。 细胞的重复使用意味着现有的细胞在视野之外(例如,当您向下滚动时从顶部的屏幕出来的细胞是从底部再次回来的细胞)。图片不正确 但是一旦单元出现,获取正确图像的代码就会执行,并且您将获得正确的图像。
您可以在单元格的“prepareForReuse”方法中使用占位符。 这个函数主要用于当单元格被重用时需要重置的值。 在这里设置占位符将确保您不会得到任何不正确的图像。
////.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"]];