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

Why is CIContext.createCGImage causing a memory leak?

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

我怀疑这可能是SDK的一个bug,我已经打开了苹果的雷达(22644754),但我觉得很奇怪,我觉得我可能错过了一个电话或一个步骤以避免泄漏。

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

如果您查看 Xcode 中的 "Memory Report",内存增加在 "Other Processes" 部分可见。

基本上,导致问题的原因如下(我已将代码严格简化为重现泄漏所必需的部分):

首先,我创建了一个由 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 作为自动管理对象)。

如果渲染代码执行的次数足够多,内存会增长太多,以至于 运行ning 应用程序会被杀死。

一个有趣的观察是,销毁 CIContext 没有什么不同,但销毁 EAGLContext 会 return 占用内存。这让我认为泄漏发生在 OpenGL 端。

我的代码中是否遗漏了任何可能导致泄漏的内容?我可以打电话释放 EAGLContext 占用的内存吗? (一直重新创建它不是一种选择,因为它是一项代价高昂的操作)。


我创建了一个简单的项目来重现该问题。您可以在以下位置找到它:

https://www.dropbox.com/s/zm19u8rmujv6jet/EAGLContextLeakDemo.zip?dl=0

重现步骤为:

  1. 打开并 运行 设备上的附加项目。
  2. 观察 Xcode 上的 "Memory Report" 屏幕。增长将在 "Usage Comparison" 饼图的 "Other Processes" 部分看到。
  3. 该应用程序显示三个按钮。他们每个人都会执行 createCGImage 命令一定次数(显示在按钮标签上)。
  4. 点击任何按钮都会导致 "Other Processes" 的内存使用量增加。在执行几次 createCGImage 调用后,这可能会更加明显。
  5. 点击 100 Renders 按钮将更清楚地显示效果。
  6. 当内存增长过快时,App会崩溃

我解决它的方法是将上下文渲染到缓冲区中,然后将其以 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);
}

这已被确认为 iOS 9 上的错误。此问题已从 iOS 9.1 beta 3 开始得到解决。

原题贴出的代码是正确的。 iOS 9 之前的版本或从 iOS 9.1 Beta 3.

开始的版本无需修改即可防止泄漏