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:
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:
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);
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;
}
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 releaseimageBuffer
: returned from a function with Get
, not Copy
or Create
, so it's not retained, so must not releaserawImageBytes
: returned from Create
, so it's retained, so must releasecolorSpace
: returned from Create
, so it's retained, so must releasenewContext
: returned from Create
, so it's retained, so must releasenewImage
: returned from Create
, so it's retained, so must releaseIn writeToSubdirectoryWithImageRef:
:
imageRef
, orientation
, and metadata
: they're parameters, so obviously you didn't retain it, so you must not release itmutableMetadata
: returned from Create
, hence retained, so you must release itmyKey
: not retained, so must not release itmyValue
: returned from Create
, so it's retained, so releaseurl
: 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 releasedestination
: returned from Create
, so it's retained, so releaseSee? It's all pretty easy.