【问题标题】:iPhone - Most memory effecient way to initialize an image?iPhone - 初始化图像的最节省内存的方法?
【发布时间】:2010-10-22 19:48:35
【问题描述】:

我读到 imageNamed: 在尝试初始化图像时很糟糕。但是,最好的方法是什么?我正在使用 imageWithContentsOfFile: 并在我的资源文件夹中传递图像的路径

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

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

现在,当我使用仪器运行我的应用程序时,我发现 NSString 占用了大量内存来执行上述操作,其中我们使用字符串文字 (@"jpg") Instruments 将负责的调用者显示为 [NSBundle mainBundle],当我使用字符串文字作为类型时,这又指向该行。

那么在不占用太多内存的情况下初始化图像最有效的方法是什么?

我把语句改成

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

其中extn 是静态的并初始化为@"jpg"fileName 在 for 循环的每次迭代中不断变化。但即便如此,根据 Instruments 的说法,NSString 的最大使用量还是因为 [NSBundle mainBundle][NSBundle pathForResource:OfType:]

【问题讨论】:

    标签: iphone memory-management


    【解决方案1】:

    我会避免在循环中使用自动释放的对象。如果 Instruments 在 NSBundle 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 次循环执行一次,而不是每次执行。此外,retain 和 release 上的 resourcePath 可能是多余的,但我把它放在那里以防你想在这里的某个地方使用自己的自动释放池。

    【讨论】:

    • Brad,我有一个疑问:我们不能直接使用imageWithContentsOfFileinitWithContentsOfFile 来加载图像,而不是分配NSData 然后从中加载UIImage?这三种方法中的哪一种会更快,更少的内存消耗(高效)?请发表您的意见。
    • @rohan-patel - 这个答案已经四年了,所以我不确定我为什么选择专门做上述事情,但-imageWithContentsOfFile: 会生成自动释放的对象。这是我们在这里试图避免的一件事。 -initWithContentsOfFile: 可能是一种更好的方法,但我可能已经对其进行了基准测试,发现当时它还不够。这是我首先要介绍的内容。
    • 感谢您的评论。如果我们在一般情况下思考(忘记这个循环场景的情况),哪个会更有效?我认为imageWithContentsOfFile: 可能有效。
    【解决方案2】:

    imageNamed: 在某些情况下很糟糕,因为它会在加载后缓存图像。因此,如果您要重用图像(很可能是您的应用程序包中的某些内容),则使用 imageNamed: 非常好。但是,如果您有很多不同的图像,并且您只是偶尔加载一个特定的图像,那么您会想要避免它。

    如果你不想使用imageNamed:,第一段代码就可以了。如果您担心循环中创建的临时字符串,请将其放在循环之前:

    NSAutoreleasePool * pool = [NSAutoreleasePool new];
    

    之后:

    [pool release];
    

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

    【讨论】:

      【解决方案3】:

      您可以做的是确保在循环中释放自动释放的对象

      之前:

      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. UIImage 应该比字符串占用更多的内存 (>= 100x)。
      2. 如果您的循环只有 30 次迭代,并且您到达事件循环,自动释放池将被释放,因此您应该不会看到超过 30 个活着的字符串。 (没有 AP 在循环中的事件)

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

      【讨论】:

      • 谢谢。我仔细检查了一遍。 Instruments 指向 UIImage 行并显示 NSString 的对象分配。除了图像名称和扩展名之外,该行中没有 NSString 值。
      • 有什么办法可以确保我没有遗漏任何东西吗?
      • 您是否在检查泄漏或对象分配?
      • 我正在检查对象分配
      【解决方案4】:

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

      我也坚定地支持“不用担心”阵营。与即使是非常小的图像所占用的内存相比,几十个 NSString 实例也只是噪声。如果您的循环从 30 张图像增加到几千张,或者其他什么,那么您可能需要考虑在循环内创建一个 NSAutoreleasePool。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-26
        • 2014-01-07
        • 1970-01-01
        • 2015-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多