Dispelling the UIImage imageNamed: FUD

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

我看到很多人说 imageNamed很糟糕,但也有相同数量的人说性能很好——特别是在渲染 UITableView的时候。例如,请参阅 this SO question或 iphonedevelopertips.com 上的 这篇文章

UIImageimageNamed方法用于泄漏,因此它是最好的避免,但已在最近的版本中修复。我想更好地理解缓存算法,以便做出一个合理的决定,在哪里我可以信任系统缓存我的图像,在哪里我需要去额外的英里和做自己。我目前的基本理解是,它是由文件名引用的 UIImages的简单 NSMutableDictionary。它变得越来越大,当内存耗尽时,它就变得越来越小。

例如,是否有人确切地知道 imageNamed后面的图像缓存不响应 didReceiveMemoryWarning?苹果似乎不太可能不这么做。

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

31053 次浏览

根据我的经验,imageNamed 创建的图像缓存不响应内存警告。我已经有两个应用程序,我可以让他们尽可能精简到 mem 管理,但仍然莫名其妙地崩溃,由于缺乏 mem。当我停止使用 imageNamed 加载图像时,两个应用程序都变得非常稳定。

我承认这两个应用程序都加载了有点大的图像,但没有什么是完全不同寻常的。在第一个应用程序中,我完全跳过了缓存,因为用户不太可能两次返回同一个图像。在第二部分中,我构建了一个非常简单的缓存类,正如您所提到的那样——将 UIImages 保存在 NSMutableDictionary 中,然后在收到内存警告时刷新其内容。如果 imageNamed: 像这样缓存,那么我就不应该看到任何性能升级。所有这些都是在2.2上运行的,我不知道这是否有任何3.0的含义。

你可以在我的第一个应用程序中找到关于这个问题的其他问题: 关于 UIImage 缓存的 StackOverflow 问题

另外一个注意事项—— InterfaceBuilder 在封面下使用 imageNamed。如果你遇到这个问题,一定要记住。

Tldr: ImagedNamed 很好。它可以很好地处理内存。使用它,不要再担心了。

编辑2012年11月 : 注意这个问题可以追溯到 iOS 2.0!此后,图像需求和处理发生了很大变化。视网膜使图像变大,加载过程稍微复杂一些。由于内置了对 iPad 和 retina 图像的支持,你当然应该在代码中使用 ImageNamed。现在,为了子孙后代:

苹果开发论坛上的 姐妹线获得了一些更好的流量,特别是 林斯温德增加了一些权威性。

在 iPhone OS 2.x 中存在这样的问题: 即使在内存警告之后,image Named: cache 也不会被清除。与此同时,+ imageNamed: 已经得到了很多使用,不是为了缓存,而是为了方便,这可能放大了问题比它应该。

同时警告

在速度方面,人们对正在发生的事情普遍存在误解。+ imageNamed: 所做的最大的事情是解码源文件中的图像数据,这几乎总是显著地增大数据大小(例如,屏幕大小的 PNG 文件在压缩时可能会消耗几十 KB,但是会消耗超过半 MB 的解压缩宽度 * 高度 * 4)。相比之下,+ imageWithContentsOfFile: 将在每次需要图像数据时解压缩该图像。正如您可以想象的那样,如果您只需要一次图像数据,那么除了拥有一个图像的缓存版本,并且可能比您需要的时间更长之外,您在这里什么也没有得到。然而,如果你确实有一个需要经常重新绘制的大图像,那么还有其他选择,尽管我主要推荐的是避免重新绘制那个大图像:)。

就缓存的一般行为而言,它基于文件名进行缓存(所以两个实例的 + imageNamed: 使用相同的名称应该导致引用相同的缓存数据) ,当你通过 + imageNamed: 请求更多的图像时,缓存将动态增长。在 iPhone OS 2.x 上,当收到内存警告时,一个 bug 会阻止缓存收缩。

还有

我的理解是,+ imageNamed: cache 应该尊重 iPhone OS 3.0上的内存警告。有机会时进行测试,如果发现情况并非如此,则报告错误。

所以,就是这样。不会打碎你的窗户或者谋杀你的孩子。这很简单,但它是一个优化工具。遗憾的是,它的名字很糟糕,而且没有像它那样易于使用的对等物——因此,人们过度使用它,当它只是履行其职责时,就会感到不安

为了解决这个问题,我在 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);

这段代码的折衷之处在于,解码后的图像使用了更多的内存,但是渲染速度更快。