117

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

我看到很多人说imageNamed性能不好,但同样数量的人说性能好——尤其是在渲染UITableViews 时。例如,请参阅此 SO 问题或iPhoneDeveloperTips.com 上的这篇文章

UIImageimageNamed方法用于泄漏,因此最好避免,但已在最近的版本中修复。我想更好地了解缓存算法,以便合理地决定我可以信任系统在哪里缓存我的图像,以及我需要在哪里加倍努力并自己做。我目前的基本理解是它是一个简单NSMutableDictionaryUIImages文件名引用。它变得更大,当内存用完时,它会变得更小。

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

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

4

2 回答 2

85

tldr:ImagedNamed 很好。它很好地处理内存。使用它并停止担心。

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

Apple Dev Forums 上的姊妹主题获得了更好的流量。具体来说,Rincewind增加了一些权限。

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

同时警告说

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

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

我的理解是 +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);

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

于 2009-06-02T07:33:52.963 回答
5

根据我的经验,由 imageNamed 创建的图像缓存不会响应内存警告。就内存管理而言,我有两个应用程序尽可能精简,但由于缺少内存,仍然莫名其妙地崩溃。当我停止使用 imageNamed 加载图像时,这两个应用程序都变得更加稳定。

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

您可以从我的第一个应用程序中找到关于此问题的其他问题: StackOverflow question about UIImage cacheing

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

于 2009-06-02T02:22:03.463 回答