I'm building an app that uploads resized versions of many images to the server, and I seem to have a memory leak that I can't track down. Here's my method that creates the multi-part request (using 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"];
}
}
}];
This is part of a method that is called from the main() function of a NSOperation on a background queue.
Here's are the photo methods that get me the actual photo data. The meat of it is in //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);
}
Note that I started using these after I experienced a similar memory leak using UIImageJPEGRepresentation() on a method that turned the CGImageRef into a UIImageinstead.
I'm pretty sure that there's a memory leak because the app eventually terminates due to a memory error. When I run instruments on it, there are tons of living malloc 464/592/398 KB allocations, which instruments gives this stack for:
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:]
Is this a bug in CGImageDestinationFinalize or am I responsible for freeing something I'm not?
Thanks in advance.