Question

Not really familiar with memory management (only recently started on my journey into iOS I am pampered by ARC), I wanted to be a good girl and avoid leaking as well as I knew how to.

I capture the still image from a video connection with AVFoundations captureStillImageAsynchronouslyFromConnection, because I want access to the image bytes. I am also using Core Foundation and Core Graphics.

Now I get a bad access exception, when iOS finishes the capture block and tries to release objects. I must have overdone it.

I think I have correctly:

  • released all the Core Graphics thingies I create in the correct sequence
  • neither retained nor released the CVImageBufferRef, because that is just a Get-something and keeps the original ownership
  • released my copy of the camera buffers metadata

EDIT: Added an optimization to my example code, curtesy of Matthias Bauch: there was an error in the memory handling for CFDataCreateWithBytesNoCopy, which I CFRelease correctly now. This was not causing the problem.

EDIT 2: Thanks to Matthias Bauch, I have been able to narrow it down to a called method. Leaving everything else inside but that method, I can make as many snapshots as I like without exceptions. I added the code of that one below the captureStillImageAsynchronouslyFromConnection block which calls it. I will continue in this way to find out what is wrong...

EDIT 3: Inside the called method, there were 2 thingies released that I was not responsible for. Thanks to newacct, who explained it point by point, I now have a working method. I post the working code below.

captureStillImageAsynchronouslyFromConnection with block:

[[self imageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
    { // get time stamp for image capture
         NSDate *timeStamp = [NSDate date];

         //get all the metadata in the image
         CFDictionaryRef metadata = CMCopyDictionaryOfAttachments(kCFAllocatorDefault,
                                                                  imageSampleBuffer,
                                                                  kCMAttachmentMode_ShouldPropagate);

         // get image reference
         CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(imageSampleBuffer);

         // >>>>>>>>>> lock buffer address
         CVPixelBufferLockBaseAddress(imageBuffer, 0);

         //Get information about the image
         uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
         size_t dataSize = CVPixelBufferGetDataSize(imageBuffer);
         size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
         size_t width = CVPixelBufferGetWidth(imageBuffer);
         size_t height = CVPixelBufferGetHeight(imageBuffer);

         // create a pointer to the image data
         CFDataRef rawImageBytes = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, baseAddress, dataSize, kCFAllocatorNull);

         // create the color space for the current device
         CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

         //Create a bitmap context
         CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);

         // <<<<<<<<<< unlock buffer address
         CVPixelBufferUnlockBaseAddress(imageBuffer, 0);

         // release core graphics object
         CGColorSpaceRelease(colorSpace);

         // Create a bitmap image from data supplied by the context.
         CGImageRef newImage = CGBitmapContextCreateImage(newContext);

         // release core graphics object
         CGContextRelease(newContext);

         BOOL saved = FALSE;

         // save CGImage as TIFF file with Objective-C
         saved = [[self photoIOController] writeToSubdirectoryWithImageRef:newImage orientation:[self orientation] timeStamp: timeStamp andMetadata: metadata];

         // create UIImage (need to change the orientation of the image so that the image is displayed correctly)
         UIImage *image= [UIImage imageWithCGImage:newImage scale:1.0 orientation:UIImageOrientationRight];

         // release core graphics object
         CGImageRelease(newImage);

         // set property for display in StillImageViewController
         [self setStillImage: image];

         // release core foundation object
         CFRelease(rawImageBytes);

         // release core foundation object
         CFRelease(metadata);

         // send notification for the camera container view controller when the image has been taken
         [[NSNotificationCenter defaultCenter] postNotificationName:kImageCapturedSuccessfully object:nil];
    }];

The method that seems to cause the exception:

  1. the parameters:

    • imageRef is CGImageRef newImage = CGBitmapContextCreateImage(newContext);

    • orientation is a UIDeviceOrientation from UIDeviceOrientationDidChangeNotification, filtered minus the ones that I do not react to

    • timeStamp is NSDate *timeStamp = [NSDate date];

    • metadata is CFDictionaryRef metadata = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);

  2. the code:


-(BOOL) writeToSubdirectoryWithImageRef: (CGImageRef) imageRef orientation: (UIDeviceOrientation) orientation timeStamp: (NSDate*) timeStamp andMetadata: (CFDictionaryRef) metadata

{
    int imageOrientation;

    // According to Apple documentation on key kCGImagePropertyOrientation,
    /* http://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGImageProperties_Reference/Reference/reference.html#//apple_ref/doc/uid/TP40005103-CH3g-SW37 */
    // the values are the same as in TIFF and EXIF (so I use the libtiff definitions)

    switch (orientation) {
        case UIDeviceOrientationPortrait:
            imageOrientation = ORIENTATION_RIGHTTOP;
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            imageOrientation = ORIENTATION_LEFTBOT;
            break;
        case UIDeviceOrientationLandscapeLeft:
            imageOrientation = ORIENTATION_TOPLEFT;
            break;
        case UIDeviceOrientationLandscapeRight:
            imageOrientation = ORIENTATION_BOTRIGHT;
            break;

        default:
            imageOrientation = ORIENTATION_RIGHTTOP;
            break;
    }

    // mutable metadata copy
    CFMutableDictionaryRef mutableMetadata = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(metadata), metadata);

    // set the key-value-pair
    CFStringRef myKey = kCGImagePropertyOrientation; // do not release!
    CFTypeRef myValue = CFNumberCreate(NULL, kCFNumberIntType, &imageOrientation);

    CFDictionaryReplaceValue(mutableMetadata, myKey, myValue);

    // get the time stamp
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:kDermaAppPhotoTimeStampFormat];
    NSString *sTimeStamp = [dateFormatter stringFromDate:timeStamp];

    if ([self pathDermaAppSubdirectory] != nil)
    {
        NSString *filePath = [NSString stringWithFormat:(@"%@/%@%@"),[self pathDermaAppSubdirectory],sTimeStamp,kTIFFImageNameEnding];

        // log file path
        HLSLoggerDebug(@"tiff image filePath = %@",filePath);

        CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath isDirectory:NO];
        CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypeTIFF, 1, NULL); // bridge: do not release!

        CGImageDestinationAddImage(destination, imageRef, mutableMetadata);

        if (CGImageDestinationFinalize(destination))
        {
            [self setPathLastImageSave:filePath];

            // release core foundation object
            CFRelease(destination);

            // release core foundation object
            CFRelease(mutableMetadata);

            // release core foundation object
            CFRelease(myValue);

            return TRUE;
        }
        else
        {
            [self setPathLastImageSave:nil];
            HLSLoggerFatal(@"Failed to write image to %@", filePath);
        }

        // release core foundation object
        CFRelease(destination);
    }

    // release core foundation object
    CFRelease(mutableMetadata);

    // release core foundation object
    CFRelease(myValue);

    return FALSE;
}
Was it helpful?

Solution

The memory management of Core Foundation objects are exactly like the memory management of Objective-C objects in Cocoa -- if you retained it, you must release it; if you didn't retain it, you must not release it. The naming conventions are slightly different. Whereas the "retaining" methods in Cocoa have names starting with alloc, retain, new, copy, and mutableCopy, in Core Foundation, it's if the function name contains Create or Copy.

So with that in mind, let's look at your code.

In the first piece of code:

  • metadata: returned from Copy, so it's retained, so must release
  • imageBuffer: returned from a function with Get, not Copy or Create, so it's not retained, so must not release
  • rawImageBytes: returned from Create, so it's retained, so must release
  • colorSpace: returned from Create, so it's retained, so must release
  • newContext: returned from Create, so it's retained, so must release
  • newImage: returned from Create, so it's retained, so must release

In writeToSubdirectoryWithImageRef::

  • imageRef, orientation, and metadata: they're parameters, so obviously you didn't retain it, so you must not release it
  • mutableMetadata: returned from Create, hence retained, so you must release it
  • myKey: not retained, so must not release it
  • myValue: returned from Create, so it's retained, so release
  • url: it's bridged directly (not bridge-retained or bridge-transfered), and the method name doesn't have any of the retaining prefixes, so it's not retained, so must not release
  • destination: returned from Create, so it's retained, so release

See? It's all pretty easy.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top