【问题标题】:How to properly use CFRelease in a context of captureStillImageAsynchronouslyFromConnection?如何在 captureStillImageAsynchronouslyFromConnection 的上下文中正确使用 CFRelease?
【发布时间】: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];
    }];

似乎导致异常的方法:

  1. 参数:

    • imageRefCGImageRef newImage = CGBitmapContextCreateImage(newContext);

    • orientation 是来自UIDeviceOrientationDidChangeNotificationUIDeviceOrientation,过滤掉我没有反应的那些

    • timeStampNSDate *timeStamp = [NSDate date];

    • 元数据CFDictionaryRef metadata = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);

  2. 代码:


-(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


【解决方案1】:

Core Foundation 对象的内存管理与 Cocoa 中 Objective-C 对象的内存管理一模一样——如果你保留它,你必须释放它;如果你没有保留它,你就不能释放它。命名约定略有不同。而 Cocoa 中的“保留”方法的名称以 allocretainnewcopymutableCopy 开头,而在 Core Foundation 中,如果函数名称包含 CreateCopy .

考虑到这一点,让我们看看您的代码。

在第一段代码中:

  • metadata:从Copy返回,所以保留,所以必须释放
  • imageBuffer:从带有Get的函数返回,而不是CopyCreate,所以没有保留,所以不能释放
  • rawImageBytes:从Create返回,所以保留,所以必须释放
  • colorSpace:从Create返回,所以保留,所以必须释放
  • newContext:从Create返回,所以保留,所以必须释放
  • newImage:从Create返回,所以保留,所以必须释放

writeToSubdirectoryWithImageRef::

  • imageReforientationmetadata:都是参数,显然你没有保留它,所以你不能释放它
  • mutableMetadata:从Create返回,因此保留,所以必须释放它
  • myKey:没有保留,所以不能释放它
  • myValue:从Create返回,所以保留,所以释放
  • url:是直接桥接的(不是bridge-retained或bridge-transfered),而且方法名没有任何retaining前缀,所以没有reserved,所以不能release
  • destination:从Create返回,所以保留,所以释放

看到了吗?一切都很简单。

【讨论】:

  • 非常感谢。这座桥是我真正忘记的那座。我将进行更多测试,然后(希望该课程中没有更多与内存相关的错误)接受您的答案。在任何情况下都是正确的。我只是想保留一个选项,今天可以询问更多细节......
猜你喜欢
  • 2011-04-20
  • 1970-01-01
  • 1970-01-01
  • 2021-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-04
  • 2023-03-05
相关资源
最近更新 更多