消除 UIImage imageNamed: FUD

发布于 2024-09-16 09:42:17 字数 888 浏览 7 评论 0原文

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

我看到很多人说 imageNamed 不好,但也有同样数量的人说性能很好 - 特别是在渲染 UITableView 时。例如,请参阅这个问题本文关于 iPhoneDeveloperTips.com

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

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

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

Edit Feb 2014: Note that this question dates from iOS 2.0! Image requirements and handling have moved on a lot since then. Retina makes images bigger and loading them slightly more complex. With the built in support for iPad and retina images, you should certainly use ImageNamed in your code.

I see a lot of people saying imageNamed is bad but equal numbers of people saying the performance is good - especially when rendering UITableViews. See this SO question for example or this article on iPhoneDeveloperTips.com

UIImage's imageNamed method used to leak so it was best avoided but has been fixed in recent releases. I'd like to understand the caching algorithm better in order to make a reasoned decision about where I can trust the system to cache my images and where I need to go the extra mile and do it myself. My current basic understanding is that it's a simple NSMutableDictionary of UIImages referenced by filename. It gets bigger and when memory runs out it gets a lot smaller.

For example, does anyone know for sure that the image cache behind imageNamed does not respond to didReceiveMemoryWarning? It seems unlikely that Apple would not do this.

If you have any insight into the caching algorithm, please post it here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

雨后咖啡店 2024-09-23 09:42:18

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

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

您可以从我的第一个应用程序中找到有关此问题的其他问题:
有关 UIImage 缓存的 StackOverflow 问题< /a>

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

In my experience, the image cache created by imageNamed does not respond to memory warnings. I've had two applications that were as lean as I could get them as far as mem management, but were still inexplicably crashing due to lack of mem. When I stopped using imageNamed to load the images, both applications became dramatically more stable.

I will admit that both applications loaded somewhat large images, but nothing that would be totally out of the ordinary. In the first application, I just skipped caching altogether because it was unlikely a user would come back to the same image twice. In the second, I built a really simple caching class doing just what you mentioned - keeping UIImages in an NSMutableDictionary and then flushing its contents if I received a memory warning. If imageNamed: were to cache like that, then I shouldn't have seen any performance upgrade. All of this was running on 2.2 - I don't know if there's any 3.0 implications on this.

You can find my other question around this issue from my first app here:
StackOverflow question about UIImage cacheing

One other note - InterfaceBuilder uses imageNamed under the covers. Something to keep in mind if you do run into this problem.

灯角 2024-09-23 09:42:17

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

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

Apple 开发者论坛上的姐妹帖子获得了一些更好的流量。具体来说,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);

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

tldr: ImagedNamed is fine. It handles memory well. Use it and stop worrying.

Edit Nov 2012: Note that this question dates from iOS 2.0! Image requirements and handling have moved on a lot since then. Retina makes images bigger and loading them slightly more complex. With the built in support for iPad and retina images, you should certainly use ImageNamed in your code. Now, for posterity's sake:

The sister thread on the Apple Dev Forums received some better traffic. Specifically Rincewind added some authority.

There are issues in iPhone OS 2.x where the imageNamed: cache would not be cleared, even after a memory warning. At the same time +imageNamed: has gotten a lot of use not for the cache, but for the convenience, which has probably magnified the problem more than it should have been.

whilst warning that

On the speed front, there is a general misunderstanding of what is going on. The biggest thing that +imageNamed: does is decode the image data from the source file, which almost always significantly inflates the data size (for example, a screen sized PNG file might consume a few dozen KBs when compressed, but consumes over half a MB decompressed - width * height * 4). By contrast +imageWithContentsOfFile: will decompress that image everytime the image data is needed. As you can imagine, if you only need the image data once, you've won nothing here, except to have a cached version of the image hanging around, and likely for longer than you need it. However, if you do have a large image that you need to redraw often, then there are alternatives, although the one I would recommend primarily is to avoid redrawing that large image :).

With respect to the general behavior of the cache, it does cache based on filename (so two instances of +imageNamed: with the same name should result in references to the same cached data) and the cache will grow dynamically as you request more images via +imageNamed:. On iPhone OS 2.x a bug prevents the cache from being shrunk when a memory warning is received.

and

My understanding is that the +imageNamed: cache should respect memory warnings on iPhone OS 3.0. Test it when you get a chance and report bugs if you find that this is not the case.

So, there you have it. imageNamed: will not smash your windows or murder your children. It's pretty simple but it is an optimisation tool. Sadly it is badly named and there is no equivaluent that is as easy to use - hence people overuse it and get upset when it simply does its job

I added a category to UIImage to fix that:

// 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 also included some example code to build your own optimised version. I can't see it is worth the maintentace but here it is for completeness.

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);

The trade off with this code is that the decoded image uses more memory but rendering is faster.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文