0

对内存管理不是很熟悉(最近才开始进入 iOS 的旅程,我被 ARC 宠爱了),我想成为一个好女孩,并且我知道如何避免泄漏。

我从与 AVFoundations 的视频连接中捕获静止图像captureStillImageAsynchronouslyFromConnection,因为我想访问图像字节。我也在使用 Core Foundation 和 Core Graphics。

现在,当 iOS 完成捕获块并尝试释放对象时,我得到了一个错误的访问异常。我一定做得过火了。

我有正确的:

  • 以正确的顺序发布了我创建的所有核心图形东西
  • 既没有保留也没有释放 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);

    • 方向UIDeviceOrientation来自UIDeviceOrientationDidChangeNotification,过滤减去我没有反应的那些

    • 时间戳NSDate *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;
}
4

1 回答 1

2

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

所以考虑到这一点,让我们看看你的代码。

在第一段代码中:

  • metadata: 从 中返回Copy,所以它被保留,所以必须释放
  • imageBuffer: 从带有Get, not Copyor的函数返回Create,所以它不被保留,所以不能释放
  • rawImageBytes: 从 中返回Create,所以它被保留,所以必须释放
  • colorSpace: 从 中返回Create,所以它被保留,所以必须释放
  • newContext: 从 中返回Create,所以它被保留,所以必须释放
  • newImage: 从 中返回Create,所以它被保留,所以必须释放

writeToSubdirectoryWithImageRef:

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

看?这一切都很容易。

于 2013-07-13T02:23:50.733 回答