UIGraphicsGetImageFromCurrentImageContext内存泄漏预览

时间:2011-02-25 18:28:51

标签: ios objective-c memory uikit cgcontext

我正在尝试在PDF中创建预览页面图像 但是我在释放内存方面遇到了一些问题。

我写了一个循环问题的简单测试算法, 应用程序在第40次迭代附近崩溃

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:@"myPdf.pdf"];
CFURLRef url = CFURLCreateWithFileSystemPath( NULL, (CFStringRef)pdfPath, kCFURLPOSIXPathStyle, NO );
CGPDFDocumentRef myPdf = CGPDFDocumentCreateWithURL( url );
CFRelease (url);
CGPDFPageRef page = CGPDFDocumentGetPage( myPdf, 1 );

int i=0;
while(i < 1000){

    UIGraphicsBeginImageContext(CGSizeMake(768,1024));
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
    CGContextFillRect(context,CGRectMake(0, 0, 768, 1024));
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, 0.0, 1024);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawPDFPage(context, page);
    CGContextRestoreGState(context);

    // --------------------------
    // The problem is here (without this line the application doesn't crash)
    UIImageView *backgroundImageView1 = [[UIImageView alloc] initWithImage:UIGraphicsGetImageFromCurrentImageContext()];
    // --------------------------

    UIGraphicsEndImageContext();
    [backgroundImageView1 release];

    NSLog(@"Loop: %d", i++);
}

CGPDFDocumentRelease(myPdf);

上述行似乎会产生内存泄漏, 但是, instruments 不显示内存问题;

我可以摆脱这种错误吗?有人可以用哪种方式解释我? 是否有其他方式来显示pdf的预览?

更新

我认为问题不在于方法UIImage创建的UIGraphicsGetImageFromCurrentImageContext()版本,而是使用此自动释放图片创建的UIImageView版本。

我将代码行分为三个步骤:

UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIImageView *myImageView = [[UIImageView alloc] init];
[myImageView setImage: myImage]; // Memory Leak

第一行和第二行不会造成内存泄漏,所以我认为方法UIGraphicsGetImageFromCurrentImageContext不是问题。

我也尝试了如下但问题仍然存在:

UIImageView *myImageView = [[UIImageView alloc] initWithImage:myImage];

我认为UIImageView的发行版中存在内存泄漏,其中包含具有自动释放属性的UIImage。

我尝试编写我的对象UIImageView继承UIView,如本thread中所述。

这个解决方案有效,但不是很优雅,这是一种解决方法,我宁愿使用UIImageView对象来解决内存问题。

7 个答案:

答案 0 :(得分:26)

问题在于:

UIGraphicsGetImageFromCurrentImageContext()

返回自动释放的UIImage。自动释放池保留此图像,直到您的代码将控制权返回到runloop,这是您很长时间没有做到的。要解决此问题,您必须在while循环的每次迭代(或每几次迭代)中创建并排空新的自动释放池。

答案 1 :(得分:12)

我知道这是一个古老的问题,但我刚刚将头撞在墙上几个小时。在我的应用中反复调用

UIImage *image = UIGraphicsGetImageFromCurrentImageContext()
尽管我调用了image = nil,但循环中的

确实保留了内存;不确定应用程序在释放之前会持续多长时间,但是对于我的应用程序获取内存警告然后崩溃肯定足够长。

我设法通过在@autoreleasepool中包装调用/使用来自UIGraphicsGetImageFromCurrentImageContext()的图像的代码来最终解决它。所以我有:

@autoreleasepool {
    UIImage *image = [self imageWithView:_outputImageView]; //create the image
    [movie addImage:image frameNum:i fps:kFramesPerSec];    //use the image as a frame in movie
    image = nil;
}

希望这可能有助于某人。

答案 2 :(得分:2)

为解决此问题,我做了以下工作(在Swift 4中进行了测试)。

对于从互联网下载的每个新图像(在实用程序队列中),我正在调用以下函数。在实施自动释放池之前,它在处理大约100个之后将崩溃。

为简单起见,在resizeImage函数中,我删除了所需的代码,除了autoreleasepool和正在泄漏的部分。

private func resizeImage(image: UIImage, toHeight: CGFloat) -> UIImage {
    return autoreleasepool { () -> UIImage in
        [...]

        let newImage = UIGraphicsGetImageFromCurrentImageContext() //Leaked
        UIGraphicsEndImageContext()

        return newImage!
    }

}

我希望这会有所帮助!

答案 3 :(得分:2)

对于那些尝试了上述所有解决方案但仍然存在内存泄漏的用户,请检查您是否正在使用调度队列。如果是这样,请确保将其autoreleaseFrequency设置为.workItem。否则,您在中设置的自动释放池将不会执行。

DispatchQueue(label: "imageQueue", qos: .userInitiated, autoreleaseFrequency: .workItem)

希望它会有所帮助,它困扰了我好几个小时,直到我终于意识到DispatchQueue可以容纳该块。

答案 4 :(得分:1)

此代码是否在主线程上运行? UIGraphicsGetImageFromCurrentImageContext(link)的文档说它必须以这种方式运行。

答案 5 :(得分:0)

我在使用大图像时遇到了同样的问题。向图像添加填充或应用过滤器会产生200MB的内存分配,而不会在图像显示在屏幕上时释放它。

找到解决此问题的有效方法:

extension UIImage {
  func release() -> UIImage? {
    guard let data = UIImageJPEGRepresentation(self, 1.0) else { return nil }
    return UIImage(data: data)
  }
}

也尝试使用UIImagePNGRepresentation,但它似乎也没有释放分配。

答案 6 :(得分:-1)

您的崩溃线可以像下面的

一样更新

让一个UIimage脱离循环

rendered_image = UIGraphicsGetImageFromCurrentImageContext();