为什么 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
重现步骤为:
- 打开并 运行 设备上的附加项目。
- 观察 Xcode 上的 "Memory Report" 屏幕。增长将在 "Usage Comparison" 饼图的 "Other Processes" 部分看到。
- 该应用程序显示三个按钮。他们每个人都会执行 createCGImage 命令一定次数(显示在按钮标签上)。
- 点击任何按钮都会导致 "Other Processes" 的内存使用量增加。在执行几次 createCGImage 调用后,这可能会更加明显。
- 点击 100 Renders 按钮将更清楚地显示效果。
- 当内存增长过快时,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.
开始的版本无需修改即可防止泄漏
我只在 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
重现步骤为:
- 打开并 运行 设备上的附加项目。
- 观察 Xcode 上的 "Memory Report" 屏幕。增长将在 "Usage Comparison" 饼图的 "Other Processes" 部分看到。
- 该应用程序显示三个按钮。他们每个人都会执行 createCGImage 命令一定次数(显示在按钮标签上)。
- 点击任何按钮都会导致 "Other Processes" 的内存使用量增加。在执行几次 createCGImage 调用后,这可能会更加明显。
- 点击 100 Renders 按钮将更清楚地显示效果。
- 当内存增长过快时,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.
开始的版本无需修改即可防止泄漏