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

标签 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);
}

请注意,我在将CGImageRef转换为UIImage的方法上使用UIImageJPEGRepresentation()经历了类似的内存泄漏后开始使用它们。

我非常确定内存泄漏,因为该应用最终由于内存错误而终止。当我在上面运行乐器时,有大量的动态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中的错误,还是我要释放不属于我的东西?

提前致谢。

最佳答案

我非常仔细地查看了您的代码,内存管理似乎正常了。您将关键位包含在@autoreleasepool块中,因此应清理所有租借的对象。您对CF对象的处理也看起来正确。 (诚​​然,内存错误可能很难发现...)

如果在使用系统方法UIImageJPEGRepresentation时遇到类似的泄漏,则它开始变得越来越像系统错误。您是否已针对不同的OS版本(在设备上)测试了它。此漏洞可能是iOS 7的新漏洞,而在较旧的iOS版本中未显示。如果是这样,那么重要的是让Apple快速意识到该错误,以便他们实际将其修复为7,而不是在iOS 8发布之前忽略它。

您是否已经在设备和SIM卡上对其进行了测试?我过去不止一次地花了很多时间试图在sim卡上运行代码时追踪明显的内存泄漏,只是得出的结论是,该错误实际上在系统库的模拟器实现中,并且完全相同在设备上运行时,代码未泄漏。

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

一旦前线错误筛选器出于愚蠢的原因将其退还给您,他们应将其提交给真正的工程师,他们可以确定它是否真的是框架错误。 (您能告诉我我对Apple的一级错误筛查程序不满意吗?)

关于ios - 在后台线程上使用CoreGraphics调整图像大小时发生内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23503462/

相关文章:

ios - 从 UITableView 单元格获取数据

memory - 达到理论 GPU 全局内存带宽

objective-c - 更改UIImage中的颜色

objective-c - NSXMLParser 的 objectForKey 错误

ios - react cocoa 条件延迟

java - Picasso MemoryPolicy.NO_CACHE/NO_STORE 仍在增加分配和内存消耗

c - C中当前进程的内存使用情况

ios - NSDate 的 timeIntervalSinceDate(_ :) and NSCalendar's components(_:fromDate:toDate:options:)

iphone - iPhone 二维码阅读器

ios - 动画结束后设置新的 Controller