UIGraphicsGetImageFromCurrentImageContext memory leak with previews
Asked Answered
I

6

18

I'm trying to create previews images of pages in a PDF but I have some problems with the release of memory.

I wrote a simple test algorithm that cycles on the problem, the app crashes near the 40th iteration:

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);

The above-mentioned line seems to generate a memory leak, however, instruments doesn't show memory problems;

Can I escape from this kind of mistake?someone can explain me in which way? Are there other ways to show previews of a pdf?

UPDATE

I think the problem isn't the release of UIImage created by the method UIGraphicsGetImageFromCurrentImageContext() but the release of UIImageView created with this autorelease image.

I have divided the line of code in three steps:

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

The first and second lines doesn't create memory leaks so I think that the method UIGraphicsGetImageFromCurrentImageContext is not the problem.

I also tried as follows but the problem persists:

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

I think there is a memory leak in the release of a UIImageView that contains a UIImage with the autorelease property.

I tried to write my object UIImageView inheriting a UIView as explained in this thread.

This solution works but isn't very elegant, it's a workaround, I would prefer to use the object UIImageView solving the memory problem.

Immune answered 25/2, 2011 at 18:28 Comment(2)
What instruments are you using to check memory usage? I'd look at "allocations" and "activity monitor", both of which can show memory usage that gets missed by the leaks instrument. Also, what is this code supposed to be doing anyway? You want to create 1000 image views but then get rid of them without using them?Wartime
Have you found the answer? I have the same problem :SFahlband
R
29

The problem is this:

UIGraphicsGetImageFromCurrentImageContext()

returns an autoreleased UIImage. The autorelease pool holds on to this image until your code returns control to the runloop, which you do not do for a long time. To solve this problem, you would have to create and drain a fresh autorelease pool on every iteration (or every few iterations) of your while loop.

Ryanryann answered 25/2, 2011 at 20:10 Comment(3)
I tried to create and release NSAutoreleasePool on every iteration but the memory problem persists. Thanks anyway for your response!Immune
One common mistake coders (myself included) make is to place the autorelease pool outside of the loop, not inside it. Thanks a lot for clarifying this one!Stuffing
#46903682Unchancy
H
16

I know it's an old question, but I've just been banging my head against the wall on this for a few hours. In my app repeatedly calling

UIImage *image = UIGraphicsGetImageFromCurrentImageContext()

in a loop does hold on to the memory despite me calling image = nil; Not sure how long the app would keep hold of the memory before freeing, but it's certainly long enough for my app to get a memory warning then crash.

I managed to solve it finally by wrapping the code that calls / uses the image from UIGraphicsGetImageFromCurrentImageContext() in @autoreleasepool. So I have:

@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;
}

Hope that might help someone.

Hatpin answered 26/5, 2014 at 18:0 Comment(0)
S
7

For future reference here's what I did to solve this (tested in Swift 4).

I was calling the function below for every new image downloaded from the internet (on a utility queue). Before implementing the autorelease pool it would crash after processing about 100.

For simplicity, in the resizeImage function I've removed needed code except for the autoreleasepool and the part that was leaking.

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

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

        return newImage!
    }

}

I hope this helps!

Stirk answered 6/10, 2018 at 18:20 Comment(5)
How is this different from the existing answers? You're using an autoreleasepool. At least two of the existing answers say to do that.Modal
Previous answers did not show a Swift 4 compatible autorelease pool implementation returning a value. At least I could not figure out the solution by only looking at this page, that's why I hoped my answer would help others in a similar situation.Stirk
OK, but now where's the loop? The question is about getting images in a loop. If there is no loop there is no need for an autorelease pool. I'm just trying to see if we can make your answer more useful / on point.Modal
Just edited the answer to help clarify. Thanks for your feedback matt.Stirk
Very nice! That explains beautifully.Modal
K
3

For those who tried all solution above and still has a memory leak, check if you are using a dispatch queue. If so, be sure to set its autoreleaseFrequency to .workItem. Or the autorelease pool you set up inside the will not execute.

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

Hope it helps, it has bugged me for hours until I finally realize that's DispatchQueue that is holding the block.

Kloman answered 6/1, 2020 at 18:12 Comment(1)
You mean that something like: DispatchQueue.global(qos: .default) { autoreleasepool { some stuff }} will not autorelease?Sangsanger
C
1

Is this code running on the main thread? The documentation of the UIGraphicsGetImageFromCurrentImageContext (link) says it must run that way.

Condottiere answered 25/2, 2011 at 20:6 Comment(2)
Yes, I read the Apple documentation. I call this function on main thread, thanks anyway for your response!Immune
From the latest UIKit Documentation on UIGraphicsGetImageFromCurrentImageContext: In iOS 4 and later, you may call this function from any thread of your app.Kan
C
-1

your line of crash you can update it like following

get one UIimage out of loop

rendered_image = UIGraphicsGetImageFromCurrentImageContext();

Casefy answered 5/5, 2012 at 8:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.