为什么CIContext.createCGImage会导致内存泄漏?

7

我只在iOS 9上观察到了这种行为;iOS 8可以正常工作。

我怀疑这可能是SDK上的一个bug,我已向苹果开发者提交了一个radar(编号22644754),但我觉得很奇怪,我有一种感觉可能是我错过了某个调用或步骤来避免泄漏。

我所观察到的是,每次调用CIContext.createCGImage时,都会增加内存使用量。棘手的部分是,内存增加发生在应用程序之外。

如果您查看Xcode的“内存报告”,则可以在“其他进程”部分看到内存增加。

基本上,我为了引起问题而做的是以下操作(我已经简化了代码,只保留了必要的部分以重现泄漏):

首先,我创建了一个由EAGLContext支持的CIContext:

let glContext = EAGLContext(API: .OpenGLES2)!
let ciContext = CIContext(EAGLContext: glContext, options: [kCIContextOutputColorSpace : NSNull()])

然后,我使用以下代码渲染图像:

let input = CIImage(image: UIImage(named: "DummyImage")!)!
ciContext.createCGImage(input, fromRect: input.extent)

DummyImage只是一个示例图像文件。泄漏与此图像的大小直接相关,因此最好使用大图像以使问题更加显着。
如您所见,我没有使用任何CIFilters(使用它们会导致相同的结果),也没有捕获生成的图像(即使我捕获了它,我也不能使用CGImageRelease,因为这些对象是自动管理的)。
如果渲染代码执行足够多次,则内存将增长得如此之多,以至于正在运行的应用程序将被杀死。
有趣的观察是销毁CIContext并不会有任何区别,但是销毁EAGLContext确实会返回占用的内存。这使我认为泄漏发生在OpenGL侧。
我的代码中是否缺少任何可能导致泄漏的内容?我能做出哪些调用以释放EAGLContext所占用的内存?(每次重新创建它都不是一种选择,因为这是一项代价高昂的操作)。
我创建了一个简单的项目来重现此问题。 您可以在以下位置找到它: https://www.dropbox.com/s/zm19u8rmujv6jet/EAGLContextLeakDemo.zip?dl=0 重现步骤如下:
  1. 在设备上打开并运行附加的项目。
  2. 观察Xcode上的“内存报告”屏幕。增长将在“使用情况比较”饼图的“其他进程”部分中看到。
  3. 应用程序显示三个按钮。每个按钮将执行createCGImage命令特定次数(显示在按钮标签上)。
  4. 点击任何一个按钮都会导致“其他进程”的内存使用增加。这可能在执行多个createCGImage调用后更为明显。
  5. 点击100次渲染按钮将更清楚地显示效果。
  6. 当内存增长过多时,应用程序将崩溃。
2个回答

1
这已被确认为iOS 9上的一个bug。该问题已在iOS 9.1 Beta 3中得到解决。
原问题中发布的代码是正确的。在iOS 9之前的版本或从iOS 9.1 Beta 3开始的版本中,不需要进行任何修改以防止泄漏。

1
我解决这个问题的方法是将上下文渲染到缓冲区中,然后将其作为JPG写入文件。我还需要进一步研究如何在旧版iOS设备上优化此流程,但与createCGImage相比,它似乎工作得很好。此代码还可用于将CIImage转换为JPEG或位图NSData。完整的示例代码可以在此处查看:

https://github.com/mmackh/IPDFCameraViewController

static CIContext *ctx = nil;

if (!ctx)
{
    ctx = [CIContext contextWithOptions:@{kCIContextWorkingColorSpace:[NSNull null]}];
}

CGSize bounds = enhancedImage.extent.size;
int bytesPerPixel = 8;
uint rowBytes = bytesPerPixel * bounds.width;
uint totalBytes = rowBytes * bounds.height;
uint8_t *byteBuffer = malloc(totalBytes);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

[ctx render:enhancedImage toBitmap:byteBuffer rowBytes:rowBytes bounds:enhancedImage.extent format:kCIFormatRGBA8 colorSpace:colorSpace];

CGContextRef bitmapContext = CGBitmapContextCreate(byteBuffer,bounds.width,bounds.height,bytesPerPixel,rowBytes,colorSpace,kCGImageAlphaNoneSkipLast);

CGImageRef imgRef = CGBitmapContextCreateImage(bitmapContext);

CGColorSpaceRelease(colorSpace);
CGContextRelease(bitmapContext);
free(byteBuffer);

if (imgRef == NULL) { goto release; }


CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImage(destination, imgRef, nil);
CGImageDestinationFinalize(destination);
CFRelease(destination);

success = YES;

goto release;

release :
{
    CFRelease(imgRef);

    if (success)
    {
        //completionHandler(filePath);
    }

    dispatch_resume(_captureQueue);
}

为什么 bytesPerPixel = 8kCIFormatRGBA8 不是每像素4字节的格式吗? - Jean-Denis Muys

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