Another iPhone - CGBitmapContextCreateImage Leak

2020-02-05 12:04发布

问题:

Like in this post:

  • iPhone - UIImage Leak, ObjectAlloc Building

I'm having a similar problem. The pointer from the malloc in create_bitmap_data_provider is never freed. I've verified that the associated image object is eventually released, just not the provider's allocation. Should I explicitly create a data provider and somehow manage it's memory? Seems like a hack.

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, blah blah blah);
CGColorSpaceRelease(colorSpace);

// ... draw into context

CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage * image = [[UIImage alloc] initWithCGImage:imageRef];

CGImageRelease(imageRef);
CGContextRelease(context);

After fbrereto's answer below, I changed the code to this:

- (UIImage *)modifiedImage {
    CGSize size = CGSizeMake(width, height);

    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();

    // draw into context   

    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;  // image retainCount = 1
}    

// caller: 
{
    UIImage * image = [self modifiedImage]; 
    _imageView.image = image; // image retainCount = 2
}

// after caller done, image retainCount = 1, autoreleased object lost its scope

Unfortunately, this still exhibits the same issue with a side effect of flipping the image horizontally. It appears to do the same thing with CGBitmapContextCreateImage internally.

I have verified my object's dealloc is called. The retainCount on the _imageView.image and the _imageView are both 1 before I release the _imageView. This really doesn't make sense. Others seem to have this issue as well, I'm the last one to suspect the SDK, but could there be an iPhone SDK bug here???

回答1:

It looks like the problem is that you are releasing a pointer to the returned CGImage, rather than the CGImage itself. I too was having similar issues before with continual growing allocations and an eventual app crash. I addressed it by allocating a CGImage rather than a CGImageRef. After the changes run your code in Insturments with allocations, and you should not see anymore perpetual memory consumption from malloc. As well if you use the class method imageWithCGImage you will not have to worry about autoreleasing your UIImage later on.

I typed this on a PC so if you drop it right into XCode you may have syntax issue, I appologize in advance; however the principal is sound.

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
CGContextRef context = CGBitmapContextCreate(NULL, blah blah blah); 
CGColorSpaceRelease(colorSpace);

// ... draw into context  

CGImage cgImage = CGBitmapContextCreateImage(context); 
UIImage * image = [UIImage imageWithCGImage:cgImage];  
CGImageRelease(cgImage); 
CGContextRelease(context);
return image;


回答2:

I had this problem and it drove me nuts for a few days. After much digging and noticing a leak in CG Raster Data using Instruments.

The problem seems to lie inside CoreGraphics. My problem was when i was using CGBitmapContextCreateImage in a tight loop it would over a period of time retain some images (800kb each) and this slowly leaked out.

After a few days of tracing with Instruments I found a workaround was to use CGDataProviderCreateWithData method instead. The interesting thing was the output was the same CGImageRef but this time there would be no CG Raster Data used in VM by Core graphics and no leak. Im assuming this is an internal problem or were misusing it.

Here is the code that saved me:

@autoreleasepool {
    CGImageRef cgImage;
    CreateCGImageFromCVPixelBuffer(pixelBuffer,&cgImage);

    UIImage *image= [UIImage imageWithCGImage:cgImage scale:1.0 orientation:UIImageOrientationUp];

    // DO SOMETHING HERE WITH IMAGE

    CGImageRelease(cgImage);
}

The key was using CGDataProviderCreateWithData in the method below.

static OSStatus CreateCGImageFromCVPixelBuffer(CVPixelBufferRef pixelBuffer, CGImageRef *imageOut)
    {
        OSStatus err = noErr;
        OSType sourcePixelFormat;
        size_t width, height, sourceRowBytes;
        void *sourceBaseAddr = NULL;
        CGBitmapInfo bitmapInfo;
        CGColorSpaceRef colorspace = NULL;
        CGDataProviderRef provider = NULL;
        CGImageRef image = NULL;

        sourcePixelFormat = CVPixelBufferGetPixelFormatType( pixelBuffer );
        if ( kCVPixelFormatType_32ARGB == sourcePixelFormat )
            bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipFirst;
        else if ( kCVPixelFormatType_32BGRA == sourcePixelFormat )
            bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst;
        else
            return -95014; // only uncompressed pixel formats

        sourceRowBytes = CVPixelBufferGetBytesPerRow( pixelBuffer );
        width = CVPixelBufferGetWidth( pixelBuffer );
        height = CVPixelBufferGetHeight( pixelBuffer );

        CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
        sourceBaseAddr = CVPixelBufferGetBaseAddress( pixelBuffer );

        colorspace = CGColorSpaceCreateDeviceRGB();

        CVPixelBufferRetain( pixelBuffer );
        provider = CGDataProviderCreateWithData( (void *)pixelBuffer, sourceBaseAddr, sourceRowBytes * height, ReleaseCVPixelBuffer);
        image = CGImageCreate(width, height, 8, 32, sourceRowBytes, colorspace, bitmapInfo, provider, NULL, true, kCGRenderingIntentDefault);

        if ( err && image ) {
            CGImageRelease( image );
            image = NULL;
        }
        if ( provider ) CGDataProviderRelease( provider );
        if ( colorspace ) CGColorSpaceRelease( colorspace );
        *imageOut = image;
        return err;
    }

    static void ReleaseCVPixelBuffer(void *pixel, const void *data, size_t size)
    {
        CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)pixel;
        CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );
        CVPixelBufferRelease( pixelBuffer );
    }


回答3:

Instead of manually creating your CGContextRef I'd suggest you leverage UIGraphicsBeginImageContext as demonstrated in this post. More details on that set of routines can be found here. I trust it'll help to resolve this issue, or at the very least give you less memory you have to manage yourself.

UPDATE:

Given the new code, the retainCount of the UIImage as it comes out of the function will be 1, and assigning it to the imageView's image will cause it to bump to 2. At that point deallocating the imageView will leave the retainCount of the UIImage to be 1, resulting in a leak. It is important, then, after assigning the UIImage to the imageView, to release it. It may look a bit strange, but it will cause the retainCount to be properly set to 1.



回答4:

You're not the only one with this problem. I've had major problems with CGBitmapContextCreateImage(). When you turn on Zombie mode, it even warns you that memory is released twice (when it's not the case). There's definitely a problem when mixing CG* stuff with UI* stuff. I'm still trying to figure out how to code around this issue.

Side note: calling UIGraphicsBeginImageContext is not thread-safe. Be careful.



回答5:

This really helped me! Here's how I used it to fix that nasty leak problem:

    CGImage *cgImage = CGBitmapContextCreateImage(context);
    CFDataRef dataRef = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
    CGImageRelease(cgImage);
    image->imageRef = dataRef;
    image->image = CFDataGetBytePtr(dataRef);

Notice, I had to store the CFDataRef (for a CFRelease(image->imageRef)) in my ~Image function. Hopefully this also helps others...JR