iPhone - 初始化图片最节省内存的方法?

4

我读到了使用 imageNamed: 初始化图片是不好的,但是最好的方法是什么呢?我正在使用 imageWithContentsOfFile: 并传递资源文件夹中图像的路径。

[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"]

这个调用在一个for循环中被执行了大约30次。

现在当我使用instruments运行我的应用程序时,我发现很多内存被NSString所占用,比如上面的操作,我们使用字符串字面量(@"jpg")。 Instruments显示责任调用者为[NSBundle mainBundle],这又指向了当我使用字符串字面量作为类型时的那一行。

那么初始化图像的最有效方法是什么,而不会使用太多的内存呢?

我把语句改成了

img = [UIImage imageWithContentsOfFile:[bndl pathForResource:fileName ofType:extn]]

其中extn是静态的并初始化为@"jpg"fileName在for循环的每次迭代中都会改变。但即使如此,根据Instruments的数据显示,[NSBundle mainBundle][NSBundle pathForResource:OfType:]仍然是NSString最常用的方法。

4个回答

5

我建议您在循环中尽可能避免使用自动释放的对象。如果Instruments报告NSBudle pathForResource:ofType:调用次数过多,建议将部分处理移出循环。

我的建议实现代码如下:

NSString *resourcePath = [[[NSBundle mainBundle] resourcePath] retain];

for (int i = 0; i < 1000; ++i) 
{   
    ...

    NSString *pathForImageFile = [resourcePath stringByAppendingPathComponent:fileName];
    NSData *imageData = [[NSData alloc] initWithContentsOfFile:pathForImageFile];

    UIImage *image = [[UIImage alloc] initWithData:imageData];
    [imageData release];

    ... 

    [image release];
}

[resourcePath release];

你将会累积一个自动释放的字符串(pathForImageFile),但这不应该是太糟糕的。你可以在循环内创建并释放一个自动释放池,但我建议每 10 或 100 次循环执行一次,而不是每次都执行。此外,在 resourcePath 上的保留和释放可能是多余的,但我将其放在那里,以防你想在此处使用自己的自动释放池。


Brad,我有一个问题:我们是否可以直接使用imageWithContentsOfFileinitWithContentsOfFile来加载图像,而不是分配NSData然后从中加载UIImage?这三种方法中哪一种更快,占用内存更少(更有效)?请发表您的评论。 - rohan-patel
@rohan-patel - 这个答案已经四年了,所以我不确定我当时为什么选择上述方法,但是-imageWithContentsOfFile:会生成自动释放的对象。这是我们试图避免的一件事情。-initWithContentsOfFile:可能是一个更好的方法,但当时可能我已经进行了基准测试并发现其表现不佳。这是我首先需要分析的事情。 - Brad Larson
感谢您的评论。如果我们从一般情况考虑(忘记这个循环场景),哪种方法更有效?我认为imageWithContentsOfFile:可能更有效。 - rohan-patel

2
imageNamed:在某些情况下并不好,因为它在加载图像后会缓存它。所以,如果您要重复使用图像——这是您的应用程序捆绑包中的一个相当常见的情况——使用imageNamed:就可以了。但是,如果您有很多不同的图像,并且只偶尔加载特定的图像,则需要避免使用它。
如果您不想使用imageNamed:,第一段代码是完全可以的。如果您担心循环中创建的临时字符串,可以在循环之前加上以下代码:
NSAutoreleasePool * pool = [NSAutoreleasePool new];

并且,在此之后:

[pool release];

这将确保循环内的任何临时对象在循环退出后被释放。然而,请确保您想要保留的任何临时对象都被保留。(例如,图像本身需要被添加到一个数据结构中以保留它们,比如数组、字典或集合,或者手动保留。)


1
你可以做的是确保你在循环内释放自动释放的对象。
先前:
for (int i = 0; i < 1000; ++i) 
{   
   UImage* img = [UIImage imageWithContentsOfFile:
        [bndl pathForResource:fileName ofType:extn]];  
   ... 
}

之后:

for (int i = 0; i < 1000; ++i) 
{   
   NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
   UImage* img = [UIImage imageWithContentsOfFile:
        [bndl pathForResource:fileName ofType:extn]];  
   ... 
   [ap release];
}

但我怀疑那些NSString实例不会引起太多麻烦:

  1. UIImages应该比字符串占用更多的内存(>= 100倍)。
  2. 如果您的循环只有30次迭代,并且您到达事件循环,自动释放池将被释放,因此您不应该看到超过30个字符串处于活动状态。(即使在循环中没有AP)

您确定您正确解释了Instruments的输出吗? 您确定没有其他部分的代码泄漏这些字符串吗?(问题中显示的代码看起来没问题)


谢谢。我进行了双重检查。Instruments指向UIImage行并显示NSString的对象分配。该行中除图像名称和扩展名外,没有任何NSString值。 - lostInTransit
有没有办法可以确保我没有漏掉任何东西? - lostInTransit
你是在检查内存泄漏还是对象分配? - mfazekas
我正在检查对象分配。 - lostInTransit

0

我还没有看到有人提到这一点,但是你看到那行代码创建了多个NSString实例的原因是因为pathForResource:ofType:必须将字符串连接在一起,以从各种组件(目录名称、文件名、扩展名)创建完整路径。

我坚定地站在“不用担心”的阵营中。与即使是非常小的图像所使用的内存相比,几十个NSString实例只是噪音。如果你的循环从30张图片变成了几千张之类的东西,那么你可能需要考虑在循环内部创建一个NSAutoreleasePool。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接