在后台线程上使用CoreGraphics调整图像大小时内存泄漏

时间:2014-05-06 19:36:25

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

我正在构建一个应用程序,将许多图像的已调整大小的版本上传到服务器,我似乎有一个内存泄漏,我无法追踪。这是我创建多部分请求的方法(使用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转换为UIImageinstead的方法进行了类似的内存泄漏后,我开始使用这些内容。

我非常确定存在内存泄漏,因为应用程序最终因内存错误而终止。当我运行仪器时,有大量的生活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中的错误还是我负责释放我不喜欢的东西?

提前致谢。

2 个答案:

答案 0 :(得分:0)

为什么在框架已经提供的时候需要使用如此复杂的东西。 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);

答案 1 :(得分:0)

我仔细查看了你的代码,内存管理看起来很合理。你有@autoreleasepool块中的密钥位,所以应该清理所有自动释放的对象。您对CF对象的处理看起来也是正确的。 (不可否认,内存错误很难发现......)

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

你有没有在设备和SIM卡上测试它?我在过去不止一次花了很多时间在sim上运行我的代码时试图追踪明显的内存泄漏,只是得出结论,这个bug实际上是在系统库的模拟器实现中,而且完全相同在设备上运行时代码没有泄漏。

我建议向Apple的bug报告系统提交2个错误:UIImageJPEGRepresentation方法的泄漏,以及CGImageSourceCreateThumbnailAtIndex方法的泄漏。在这两种情况下提供最少的测试应用程序,或Apple的精彩(阅读无知)错误筛选器将立即回复给您。

一旦前线漏洞检查员用尽了愚蠢的理由将其反馈给您,他们应该将其提交给真正的工程师,他们可以确定它是否真的是一个框架错误。 (你能告诉我对Apple的第一级漏洞筛选器的看法不高吗?)