消除UIImage imageNamed:FUD


117

编辑2014年2月:请注意,这个问题可以追溯到iOS 2.0!从那时起,图像要求和处理方式发生了很大变化。视网膜使图像更大,加载图像稍微复杂一些。借助对iPad和视网膜图像的内置支持,您当然应该在代码中使用ImageNamed

我看到很多人都说imageNamed不好,但同样也有很多人说性能很好-特别是在渲染UITableViews时。例如,请参阅此SO问题或在iPhoneDeveloperTips.com上查看文章

UIImageimageNamed方法曾经泄漏,因此最好避免使用,但在最新版本中已修复。我想更好地了解缓存算法,以便对我可以信任系统缓存图像的位置以及需要自己做些什么的地方做出合理的决定。我当前的基本理解是,这是一个简单NSMutableDictionaryUIImages通过文件名引用。它变得更大,内存用尽时它变得更小。

例如,是否有人可以确定后面的图像缓存imageNamed没有响应didReceiveMemoryWarning?苹果似乎不会这样做。

如果您对缓存算法有任何了解,请在此处发布。


2
我也是。似乎SO并不像我想的那样充满天才。
罗格

似乎您已经花了一些时间对此进行了调查。您是否做过任何试验,以显示最新版本的可可触摸版本对UIImage imageNamed的负面影响?创建一个UITableView并添加很多(几千)行,并查看每行显示不同图像时性能是否下降。也许那时人们可以对您的发现发表评论。
stefanB

实际上,我并没有做太多的实验。我使用imageNamed,尽管我尚未进行内存优化此应用程序,但我没有什么可以直接指向imageNamed的内存。我已经指出了一些博客帖子,抱怨这里的imagedNamed,我在苹果板上也提出了类似的问题,该问题得到了更多的解决。如果有时间,我将重新发布摘要并链接到此处,并认为它已经得到了答复。
罗格2009年

Answers:


85

tldr:ImagedNamed很好。它可以很好地处理内存。使用它,不必担心。

编辑2012年11月:请注意,这个问题可以追溯到iOS 2.0!从那时起,图像要求和处理方式发生了很大变化。视网膜使图像更大,加载图像稍微复杂一些。借助对iPad和视网膜图像的内置支持,您当然应该在代码中使用ImageNamed。现在,为了后代的缘故:

妹妹线程在苹果开发论坛收到一些更好的交通。特别是Rincewind增加了一些权限。

iPhone OS 2.x中存在一些问题,即使在出现内存警告后,imageNamed:缓存也不会被清除。同时,+ imageNamed:已得到很多使用,而不是用于高速缓存,而是为了方便起见,这可能使问题更加严重。

同时警告

在速度方面,人们对正在发生的事情普遍存在误解。+ imageNamed:要做的最大的事情是解码源文件中的图像数据,这几乎总是显着增大数据大小(例如,屏幕大小的PNG文件在压缩时可能会消耗数十KB,但会消耗一半以上的MB解压缩-宽度*高度* 4)。相比之下,+ imageWithContentsOfFile:每次需要图像数据时都会对该图像进行解压缩。可以想象,如果只需要一次图像数据,那么在这里就什么也赢不了,除了有图像的缓存版本,而且可能需要的时间更长。但是,如果确实有一个大图像需要经常重绘,那么有其他选择,尽管我主要建议您避免重绘该大图像:)。

关于缓存的一般行为,它会根据文件名进行缓存(因此+ imageNamed:具有相同名称的两个实例应导致对相同的缓存数据的引用),并且当您通过以下方式请求更多图像时,缓存将动态增长+ imageNamed :。在iPhone OS 2.x上,错误会阻止在收到内存警告时缩小缓存。

我的理解是+ imageNamed:缓存应遵守iPhone OS 3.0上的内存警告。如果有机会请进行测试,如果发现不是这种情况,请报告错误。

所以你有它。imageNamed:不会破坏您的窗户或谋杀您的孩子。这很简单,但是它是一种优化工具。可悲的是,它的名字不好用,而且没有任何一个等值的易用性-因此,人们在使用它时,过度使用它并感到不高兴。

我向UIImage添加了一个类别来解决此问题:

// header omitted
// Before you waste time editing this, please remember that a semi colon at the end of a method definition is valid and a matter of style.
+ (UIImage*)imageFromMainBundleFile:(NSString*)aFileName; {
    NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", bundlePath,aFileName]];
}

Rincewind还提供了一些示例代码来构建您自己的优化版本。我看不出值得维护,但这里是为了完整性。

CGImageRef originalImage = uiImage.CGImage;
CFDataRef imageData = CGDataProviderCopyData(
     CGImageGetDataProvider(originalImage));
CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData(imageData);
CFRelease(imageData);
CGImageRef image = CGImageCreate(
     CGImageGetWidth(originalImage),
     CGImageGetHeight(originalImage),
     CGImageGetBitsPerComponent(originalImage),
     CGImageGetBitsPerPixel(originalImage),
     CGImageGetBytesPerRow(originalImage),
     CGImageGetColorSpace(originalImage),
     CGImageGetBitmapInfo(originalImage),
     imageDataProvider,
     CGImageGetDecode(originalImage),
     CGImageGetShouldInterpolate(originalImage),
     CGImageGetRenderingIntent(originalImage));
CGDataProviderRelease(imageDataProvider);
UIImage *decompressedImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);

使用此代码的权衡在于,解码后的图像使用更多的内存,但渲染速度更快。


似乎在iOS11上,如果图像来自xcasset目录,则[UIImage imageNamed:]不会从缓存中清除图像。这会由于内存不足而导致崩溃。
朱拉·安塔斯

5

以我的经验,imageNamed创建的图像缓存不响应内存警告。我有两个应用程序,它们的精简程度可以达到内存管理的水平,但是由于缺少内存,仍然无法解释。当我停止使用imageNamed加载图像时,两个应用程序都变得更加稳定。

我承认这两个应用程序都加载了较大的图像,但是没有什么完全与众不同的。在第一个应用程序中,我只是完全跳过了缓存,因为用户不太可能两次回到相同的图像。在第二篇文章中,我构建了一个非常简单的缓存类,按照您所说的进行处理-将UIImages保留在NSMutableDictionary中,如果收到内存警告,则刷新其内容。如果imageNamed:那样缓存,那么我应该不会看到任何性能提升。所有这些都在2.2上运行-我不知道这是否对3.0有任何影响。

您可以在以下第一个应用程序中找到有关此问题的其他问题: 有关UIImage缓存的StackOverflow问题

另一注-InterfaceBuilder在幕后使用imageNamed。如果确实遇到此问题,请记住一些注意事项。


我看到了完全相同的行为,并且从文件中读取直接解决了它。另外,当我尝试加载非常大的图像-11,456 x 3,226 px,15MB时,也会发生这种情况。我的理论是系统通过ImageNamed缓存操作部分耗尽内存。在这种情况下,没有内置处理。
Dogweather
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.