【发布时间】:2013-07-11 09:34:36
【问题描述】:
不太熟悉内存管理(最近才开始我的 iOS 之旅,我被 ARC 宠爱了),我想成为一个好女孩,并且我知道如何避免泄漏。
我从与 AVFoundations captureStillImageAsynchronouslyFromConnection 的视频连接中捕获静止图像,因为我想访问图像字节。我也在使用 Core Foundation 和 Core Graphics。
现在,当 iOS 完成捕获块并尝试释放对象时,我遇到了错误的访问异常。我一定做得过火了。
我认为我猜对了:
- 按正确顺序发布了我创建的所有 Core Graphics 东西
- 既没有保留也没有释放 CVImageBufferRef,因为那只是一个 Get-something 并保留原始所有权
- 发布了我的相机缓冲区元数据副本
编辑: 为我的示例代码添加了优化,Matthias Bauch 的诅咒:CFDataCreateWithBytesNoCopy 的内存处理出现错误,我现在 CFRelease 正确。这不是导致问题的原因。
编辑 2: 感谢Matthias Bauch,我能够将其缩小到一个被调用的方法。除了那个方法之外,其他所有东西都留在里面,我可以无一例外地制作任意数量的快照。我在调用它的captureStillImageAsynchronouslyFromConnection 块下面添加了那个代码。我会继续用这种方式找出问题所在……
编辑 3: 在被调用的方法中,发布了 2 个我不负责的东西。感谢newacct逐点解释,我现在有了工作方法。我在下面发布了工作代码。
captureStillImageAsynchronouslyFromConnection 带块:
[[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];
}];
似乎导致异常的方法:
-
参数:
imageRef 是
CGImageRef newImage = CGBitmapContextCreateImage(newContext);orientation 是来自
UIDeviceOrientationDidChangeNotification的UIDeviceOrientation,过滤掉我没有反应的那些timeStamp 是
NSDate *timeStamp = [NSDate date];元数据是
CFDictionaryRef metadata = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
代码:
-(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;
}
【问题讨论】:
-
并且异常肯定是由您的版本引起的? IE。如果您注释掉这些版本,它不会引发错误的访问吗?对于调试,应该可以将它们中的每一个注释掉,以找出导致这种情况的原因。 另外:您正在泄漏
rawImageBytes,因为您再次保留了已经保留的返回值;但这可能不是例外的原因。泄漏通常只占用内存。 -
谢谢。我开始意识到我根本不知道发生了什么。我尝试了对版本的评论。一步一步地完成这一切,在我看来,错误发生在我的所有代码和 CoconutKit 库的代码都运行良好之后。我有一些屏幕截图,但我认为它们没有帮助。对它们感兴趣?
-
P.S.:我也是
CFRelease(rawImageBytes);,我以为这样可以平衡我多余的CFRetain(rawImageBytes);? -
由于这是一种异步方法,因此通过逐步调试它有点困难。可能由于并发问题而发生异常。尝试将其缩小到一种特定的方法。首先删除
[self setStillImage: image];、通知发布和所有其他代码,这些代码在“......对所有囤积的信息......做一些事情......”中调用你的其他方法。如果这在不引发异常的情况下有效,请重新添加[self setStillImage:]并查看会发生什么。等等。调试很有趣 ;-) -
跟随Create Rule
CFDataCreateWithBytesNoCopy已经返回一个保留实例(引用计数为1)。然后你再次明确地保留它(参考计数 +1 = 总计 2)。然后释放它(参考计数 -1 = 1)。就是这样。有你的泄漏。
标签: iphone automatic-ref-counting retain image-capture