【问题标题】:Memory leak when resizing images using CoreGraphics on background thread在后台线程上使用 CoreGraphics 调整图像大小时内存泄漏
【发布时间】:2014-06-23 14:01:04
【问题描述】:

我正在构建一个应用程序,该应用程序将许多图像的调整大小版本上传到服务器,但我似乎有无法追踪的内存泄漏。这是我创建多部分请求的方法(使用 Restkit 0.2):

[self.objectManager
     multipartFormRequestWithObject:nil
     method:RKRequestMethodPUT
     path:pathString
     parameters:@{@"photo": photoParamater}
     constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
       @autoreleasepool {
         if (uploadImage) {
           NSData *imageData =
           [photo scaledImageDataWithSmallerDimension:IMAGE_UPLOAD_SMALLER_DIMENSION
                                   compressionQuality:IMAGE_UPLOAD_JPEG_QUALITY];
           imageDataBytes += imageData.length;
           [formData appendPartWithFileData:imageData
                                       name:photo.file_key.absoluteString
                                   fileName:photo.filename
                                   mimeType:@"image/jpg"];
         }
       }
     }];

这是从后台队列上的 NSOperation 的 main() 函数调用的方法的一部分。

以下是获取实际照片数据的照片方法。它的肉在 //3

//1
- (NSData *)scaledImageDataWithSmallerDimension:(CGFloat)length compressionQuality:(float)quality
{
    CGSize newSize = [self scaledSizeWithSmallerDimension:length];
    return [self imageDataResizedToFitSize:newSize compressionQuality:quality];
}

//2
- (NSData *)imageDataResizedToFitSize:(CGSize)size compressionQuality:(float)quality
{
    return [self thumbnailDataForAsset:self.asset maxPixelSize:MAX(size.height, size.width) compressionQuality:quality];
}

//3
- (NSData *)thumbnailDataForAsset:(ALAsset *)asset
                     maxPixelSize:(NSUInteger)size
               compressionQuality:(float)quality {
  @autoreleasepool {
    NSParameterAssert(asset != nil);
    NSParameterAssert(size > 0);

    ALAssetRepresentation *rep = [asset defaultRepresentation];

    CGDataProviderDirectCallbacks callbacks = {
      .version = 0,
      .getBytePointer = NULL,
      .releaseBytePointer = NULL,
      .getBytesAtPosition = getAssetBytesCallback,
      .releaseInfo = releaseAssetCallback,
    };

    CGDataProviderRef provider = CGDataProviderCreateDirect((void *)CFBridgingRetain(rep),
                                                            [rep size],
                                                            &callbacks);
    CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL);

    NSDictionary *imageOptions =
    @{
      (NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
      (NSString *)kCGImageSourceThumbnailMaxPixelSize : [NSNumber numberWithUnsignedInteger:size],
      (NSString *)kCGImageSourceCreateThumbnailWithTransform : @YES,
      };
    CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source,
                                                              0,
                                                              (__bridge CFDictionaryRef) imageOptions);
    CFRelease(source);
    CFRelease(provider);

    if (!imageRef) {
      return nil;
    }


    NSMutableData *outputData = [[NSMutableData alloc] init];
    CGImageDestinationRef destRef = CGImageDestinationCreateWithData((__bridge CFMutableDataRef) outputData,
                                                                     kUTTypeJPEG,
                                                                     1,
                                                                     NULL);
    NSDictionary *imageAddOptions =
    @{
      (NSString *)kCGImageDestinationLossyCompressionQuality : [NSNumber numberWithFloat:quality],
      };
    CGImageDestinationAddImage(destRef,
                               imageRef,
                               (__bridge CFDictionaryRef) imageAddOptions);
    CGImageDestinationFinalize(destRef);
    CFRelease(imageRef);
    CFRelease(destRef);

    return outputData;
  }
}

// Helper methods for thumbnailDataForAsset:maxPixelSize:
static size_t getAssetBytesCallback(void *info, void *buffer, off_t position, size_t count) {
    ALAssetRepresentation *rep = (__bridge id)info;

    NSError *error = nil;
    size_t countRead = [rep getBytes:(uint8_t *)buffer fromOffset:position length:count error:&error];

    if (countRead == 0 && error) {
        // We have no way of passing this info back to the caller, so we log it, at least.
        NSLog(@"thumbnailForAsset:maxPixelSize: got an error reading an asset: %@", error);
    }

    return countRead;
}

static void releaseAssetCallback(void *info) {
    // The info here is an ALAssetRepresentation which we CFRetain in thumbnailDataForAsset:maxPixelSize:.
    // This release balances that retain.
    CFRelease(info);
}

请注意,在我使用 UIImageJPEGRepresentation() 在将 CGImageRef 转换为 UIImage 的方法上遇到类似的内存泄漏后,我开始使用这些。

我很确定存在内存泄漏,因为应用程序最终会因内存错误而终止。当我在其上运行仪器时,有大量的活 malloc 464/592/398 KB 分配,仪器为此堆栈提供了:

   0 libsystem_malloc.dylib malloc_zone_realloc
   1 libsystem_malloc.dylib realloc
   2 Foundation _NSMutableDataGrowBytes
   3 Foundation -[NSConcreteMutableData appendBytes:length:]
   4 ImageIO CGImageWriteSessionPutBytes
   5 ImageIO emptyOutputBuffer
   6 ImageIO encode_mcu_huff
   7 ImageIO compress_data
   8 ImageIO process_data_simple_main
   9 ImageIO _cg_jpeg_write_scanlines
  10 ImageIO writeOne
  11 ImageIO _CGImagePluginWriteJPEG
  12 ImageIO CGImageDestinationFinalize
  13 MyApp -[MyPhoto thumbnailDataForAsset:maxPixelSize:compressionQuality:] 
  14 MyApp -[MyPhoto imageDataResizedToFitSize:compressionQuality:] 
  15 MyApp -[MyPhoto scaledImageDataWithSmallerDimension:compressionQuality:] 

这是 CGImageDestinationFinalize 中的一个错误,还是我负责释放我不是的东西?

提前致谢。

【问题讨论】:

  • 我最初的反应是,您正在创建大量图像并将它们保存在内存中以供传输(appendPartData 部分)您是否可能只是内存不足而没有实际泄漏?您可以将单个图像写入临时文件,然后使用 RestKit 上传这些图像吗?
  • 感谢大卫,但多部分表单请求一次只能发布一张图片。有 2 个线程正在上传,因此在任何给定时间内存中应该只有 2 个图像。在我的 5S 上,它在内存不足之前成功地通过了数百张图像。我还有第二个函数,它做几乎完全相同的事情,但同时发布了一批 100 个缩略图,并且 not 似乎触发了这个泄漏,这让我更加困惑。
  • 我正在尝试在 OSX 上做一些几乎相同的事情并观察到同样的问题。您是否找到解决方法或以其他方式解决此问题?
  • 抱歉,我不记得了,我很快就停止了上传所有缩略图的应用程序。

标签: ios objective-c memory memory-leaks core-graphics


【解决方案1】:

我非常仔细地查看了您的代码,并且内存管理看起来不错。您将密钥位包含在 @autoreleasepool 块中,因此应清理任何自动出租的对象。您对 CF 对象的处理看起来也正确。 (诚​​然,内存错误很难发现......)

如果您在使用系统方法 UIImageJPEGRepresentation 时遇到类似的泄漏,它开始闻起来越来越像系统错误。您是否针对不同的操作系统版本(在设备上)对其进行了测试。这可能是 iOS 7 的新漏洞,并且不会出现在较旧的 iOS 版本中。如果是这样,重要的是让 Apple 迅速意识到这个错误,以便他们在 7 中实际修复它,而不是在 iOS 8 发布之前忽略它。

您是否在设备和 sim 上测试过它?在过去,我不止一次地在 sim 上运行我的代码时花费大量时间试图追踪明显的内存泄漏,只是得出的结论是该错误实际上是在系统库的模拟器实现中,并且完全相同代码在设备上运行时没有泄漏。

我建议向 Apple 的错误报告系统提交 2 个错误:UIImageJPEGRepresentation 方法中的泄漏,以及CGImageSourceCreateThumbnailAtIndex 方法中的泄漏。在这两种情况下都提供最少的测试应用程序,否则 Apple 出色的(阅读无能的)错误筛选器会立即将其退回给您。

一旦一线的 bug 筛选人员用尽愚蠢的理由将它踢回给你,他们应该将它提交给真正的工程师,他们可以确定它是否真的是框架 bug。 (您能说我对 Apple 的一级错误筛选器的评价很低吗?)

【讨论】:

  • 谢谢邓肯,我得出了同样的结论。我肯定在设备(iPhone 5S)上运行。在继续用头撞墙后,我尝试只替换对 CGImageDestinationFinalize(destRef); 的调用。使用 for 循环将 400K 的 0 放在 mutabledata 对象中,并且内存泄漏消失了,这让我非常有信心我不会以错误的方式抓住它。我将尝试制作一个重现应用程序并按照您的建议提交给 Apple。感谢您花时间详细查看我的内存管理!
【解决方案2】:

为什么框架已经提供了这么多复杂的东西。 ImageIO 框架具有一些功能,可让您从源图像创建缩略图。

CGImageSourceRef src = CGImageSourceCreateWithURL(url);
NSDictionary *options = (CFDictionaryRef)[NSDictionary 
dictionaryWithObject:[NSNumber numberWithInt:1024
forKey:(id)kCGImageSourceThumbnailMaxPixelSize];

CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src,0,options);
UIImage *image = [UIImage imageWithCGImage:thumbnail];
CGImageRelease(thumbnail);
CGImageSourceRelease(src);

【讨论】:

  • 你好 insane-36,我正在寻找一个 NSData 对象作为输出,而不是 UIImage。正如我在帖子中提到的,我尝试使用调整图像大小的函数,然后使用 UIImageJPEGRepresentation 将其转换为 NSData,但我有同样的泄漏。我尝试用我的图像调整大小代码代替您发布的内容,因为它看起来确实更简单,但我不得不对其进行一些修改(CGImageSourceCreateWithURL 采用选项字典,我从asset.defaultRepresentation.url 设置的url)并得到一个错误:: ImageIO: CGImageSourceCreateWithURL CFURLCreateDataAndPropertiesFromResource 失败,错误代码为 -11。
  • 嗯,带有 CGImageSourceCreateWithURL 的函数参数是源图像 url 和选项。仅在没有任何选项的情况下首次尝试使用 url。但是,这些选项看起来也更简单,如此处所述developer.apple.com/library/ios/documentation/graphicsimaging/…
  • 我在this gist 中尝试了这两种变体。两者都返回错误 -11。您确定 ALAssetRepresentation URL 对 CGCreateWithURL 有效吗?如果不是,上面的提供者似乎是读取资产的正确方法。
  • 顺便说一句:这是我使用的方法的博客文章:mindsea.com/2012/12/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-14
  • 1970-01-01
  • 2012-06-15
  • 2016-02-25
  • 2014-02-15
  • 2015-10-26
相关资源
最近更新 更多