NSImage drawInRect和NSView cacheDisplayInRect中的内存保留问题

5
我正在开发的应用程序处理图像。用户最多可以拖放4张图片,应用程序会根据用户选择的模板对它们进行布局。一张图片可能在最终视图中被添加2-3次。
布局中的每个图像都是在NSView中绘制的(使用NSView的drawRect方法和drawInRect方法)。现在,通过将NSView保存为图像来创建最终图像(通过布局所有图像组合而成),这一切都运作得非常好。
现在我面临的问题是,在所有处理完成后应用程序仍然保留着内存。我已经使用了仪器分配,没有发现内存泄漏,但我看到“持久字节”随着每个应用程序会话不断增加,一个用户报告了几个GB的问题。请参见截图。
当我在仪器中进一步调查时,我看到下面的应用程序代码片段导致了内存保留。所有这些都与ImageIO和CoreImages有关。请参见下面的仪器内容:
然而,这似乎只是10.10及以上系统的问题。在10.9.x中测试了同一版本的应用程序,内存使用情况仍然在60MB以内。在应用程序的会话执行期间,它会增加到200MB,但一旦完成,它就会回到50-60MB,这对于这种类型的应用程序是很正常的。
[_photoImage drawInRect: self.bounds fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
            _photoImage = nil;

上面的代码是我在NSView的drawRect方法中使用的,用于绘制图像,图像中显示的代码用于将NSView作为图像获取。
更新:经过进一步调查,我发现是CGImageSourceCreateWithData正在缓存NSImage的TIFF数据。此外,我正在使用以下代码裁剪图像,如果我取消注释它,内存消耗就会正常工作。
NSData *imgData = [imageToCrop TIFFRepresentation];
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imgData, NULL);
CGImageRef maskRef =  CGImageSourceCreateImageAtIndex(source, 0, NULL);
CGImageRef imageRef = CGImageCreateWithImageInRect(maskRef, rect);
NSImage *cropped = [[NSImage alloc] initWithCGImage: imageRef size:rect.size];

CGImageRelease(maskRef);
CGImageRelease(imageRef);
CFRelease(source);
//CFRelease( options );
imgData = nil;

我还尝试着将 kCGImageSourceShouldCache 显式地设置为 false (但它默认就是 false),但结果相同。请帮忙解决内存保留的问题。

看起来我问了一个非常简单的问题,我应该知道答案,或者是一个非常难回答的问题。:( 请帮帮我,我真的卡住了。 - M P
1个回答

4

经过大量调试后,最终发现CGImageSourceCreateWithData在某个地方会保留NSImage的TIFF数据。当我修改了这行代码:

CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imgData, NULL);

使用

CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:path], NULL);

一切都开始正常工作了,应用程序的内存使用量从300MB(6张图像)降至50-60MB,现在表现稳定。
除了上述更改,它仍然会在某处引起内存保留,因此在所有处理完成后,我清除了每个图层的图像为“nil”,这样就可以完美地工作了。我曾经认为将父级设置为“nil”也会释放图像,但那行不通。
无论如何,如果有人遇到drawInRect或cacheDisplayInRect的问题,请确保在不再需要时清除图像。
更新于2016年7月2日
我发现32位中默认情况下kCGImageSourceShouldCache为false,在64位中为true。通过将其设置为false,我能够通过以下代码释放内存。
const void *keys[] =   { kCGImageSourceShouldCache};
    const void *values[] = { kCFBooleanFalse};
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)[image TIFFRepresentation], optionsDictionary);

希望这能帮助到某些人。

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