我正在尝试通过使用基于磁盘的图像缓存而不是通过网络来提高我的图像密集型iPhone应用程序的性能。我在SDImageCache(http://github.com/rs/SDWebImage/blob/master/SDImageCache.m)之后对我的图像缓存进行了建模,并且几乎相同但没有异步缓存进/出操作。
我有一些滚动视图和表视图,它们异步加载这些图像。如果映像在磁盘上,则从映像缓存加载,否则发出网络请求,后续结果存储在缓存中。
我遇到的问题是,当我滚动浏览滚动视图或表视图时,从磁盘加载图像时会出现明显的延迟。特别是,在滚动视图中从一个页面转到另一个页面的动画在转换过程中有一个小的冻结。
我试图解决这个问题:
有没有办法让我的磁盘访问效果更好或对用户界面的影响更小?
请注意,我已经在内存中缓存了这些图像。因此,一旦将所有内容加载到内存中,UI就会很好并且响应迅速。但是当应用程序启动时,或者如果调度了低内存警告,我将会遇到许多这些UI滞后,因为图像是从磁盘加载的。
相关的代码段如下。我不认为我做任何花哨或疯狂的事情。 iPhone 3G上的延迟似乎并不明显,但在第二代iPod Touch上却非常明显。
图片缓存代码:
这是我的图片缓存代码的相关摘要。很简单。
- (BOOL)hasImageDataForURL:(NSString *)url {
return [[NSFileManager defaultManager] fileExistsAtPath:[self cacheFilePathForURL:url]];
}
- (NSData *)imageDataForURL:(NSString *)url {
NSString *filePath = [self cacheFilePathForURL:url];
// Set file last modification date to help enforce LRU caching policy
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:[NSDate date] forKey:NSFileModificationDate];
[[NSFileManager defaultManager] setAttributes:attributes ofItemAtPath:filePath error:NULL];
return [NSData dataWithContentsOfFile:filePath];
}
- (void)storeImageData:(NSData *)data forURL:(NSString *)url {
[[NSFileManager defaultManager] createFileAtPath:[self cacheFilePathForURL:url] contents:data attributes:nil];
}
滚动视图控制器代码
这是我用于在滚动视图控制器中显示图像的相关代码片段。
- (void)scrollViewDidScroll:(UIScrollView *)theScrollView {
CGFloat pageWidth = theScrollView.frame.size.width;
NSUInteger index = floor((theScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
[self loadImageFor:[NSNumber numberWithInt:index]];
[self loadImageFor:[NSNumber numberWithInt:index + 1]];
[self loadImageFor:[NSNumber numberWithInt:index - 1]];
}
- (void)loadImageFor:(NSNumber *)index {
if ([index intValue] < 0 || [index intValue] >= [self.photoData count]) {
return;
}
// ... initialize an image loader object that accesses the disk image cache or makes a network request
UIView *iew = [self.views objectForKey:index];
UIImageView *imageView = (UIImageView *) [view viewWithTag:kImageViewTag];
if (imageView.image == nil) {
NSDictionary *photo = [self.photoData objectAtIndex:[index intValue]];
[loader loadImage:[photo objectForKey:@"url"]];
}
}
图像加载器对象只是一个轻量级对象,它检查磁盘缓存并决定是否从磁盘或网络获取映像。完成后,它会调用滚动视图控制器上的方法来显示图像:
- (void)imageLoadedFor:(NSNumber *)index image:(UIImage *)image {
// Cache image in memory
// ...
UIView *view = [self.views objectForKey:index];
UIImageView *imageView = (UIImageView *) [view viewWithTag:kImageViewTag];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.image = image;
}
更新
我正在尝试使用该应用,我禁用了图像缓存并恢复为始终发出网络请求。在滚动浏览滚动视图和表视图时,看起来简单地使用网络请求来获取图像也会导致相同的延迟!也就是说,当网络请求完成并且图像显示在滚动视图页面或表格单元格中时,当我尝试拖动它时,UI略微滞后并且有几分钟的延迟。
使用磁盘缓存时滞后似乎更明显,因为滞后总是发生在页面转换时。将加载的图像分配给适当的UIImageView时,我可能做错了吗?
另外 - 我尝试过使用小图片(50x50缩略图)并且延迟似乎有所改善。因此,性能影响似乎是由于从磁盘加载大图像或将大图像加载到UIImage对象中。我想一个改进是减少加载到滚动视图和表视图中的图像的大小,这是我计划做的事情。但是,我只是不明白其他照片密集型应用程序如何能够在可滚动视图中呈现看起来非常高分辨率的照片,而不会出现进入磁盘或通过网络出现性能问题。
答案 0 :(得分:1)
您应该查看LazyTableImages示例应用程序。
答案 1 :(得分:1)
如果您已将其缩小到网络活动范围,我会尝试封装您的请求,以确保它是主线程的100%折扣。虽然您可以异步使用NSURLConnection并响应它的委托方法,但我发现在后台操作中包装同步请求更容易。如果您的需求更复杂,您可以使用NSOperation或Grand central dispatch。 imageLoader实现中的(相对)简单示例可以是:
// imageLoader.m
// assumes that that imageCache uses kvp to look for images
- (UIImage *)imageForKey:(NSString *)key
{
// check if we already have the image in memory
UImage *image = [_images objectForKey:key];
// if we don't have an image:
// 1) start a background task to load an image from a file or URL
// 2) return a default image to display while loading
if (!image) {
[self performSelectorInBackground:@selector(loadImageForKey) withObject:key];
image = [self defaultImage];
}
return image;
}
- (void)loadImageForKey:(NSString *)key
{
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
// attempt to load the image from the file cache
UIImage *image = [self imageFromFileForKey:key];
// if no image, load the image from the URL
if (!image) {
image = [self imageFromURLForKey:key];
}
// if no image, return default or imageNotFound image
if (!image) {
image = [self notFoundImage];
}
if ([_delegate respondsTo:@selector(imageLoader:didLoadImage:ForKey:)]) {
[_delegate imageLoader:self didLoadImage:image forKey:key];
}
[pool release];
}
- (UIImage *)imageFromURLForKey:(NSString *)key
{
NSError *error = nil;
NSData *imageData = [NSData dataWithContentsOfURL:[self imageURLForKey:key]
options:0
error:&error];
UIImage *image;
// handle error if necessary
if (error) {
image = [self errorImage];
}
// create image from data
else {
image = [UIImage imageWithData:imageData];
}
return image;
}
答案 2 :(得分:0)
在imageview上绘制图像时,实际上会读取磁盘中的图像。即使我们从磁盘缓存图像读取它也不会影响,因为它只是保持对文件的引用。为此,您可能必须使用较大图像的平铺。
此致 迪帕
答案 3 :(得分:0)
我遇到了这个问题 - 你在滚动时UI的加载速度有多快 - 所以我只是解决问题并改善用户体验。
答案 4 :(得分:0)
通常是图像解码需要花费时间,导致UI冻结(因为它全部发生在主线程上)。每次在大图像上调用[UIImage imageWithData:]
时,您都会注意到打嗝。解码较小的图像要快得多。
两个选项:
didFinishScrolling
上“加强”。缩略图应该足够快地解码,以便您不会跳过任何帧。我更喜欢第二种方法;现在GDC很容易。