PDA

View Full Version : Help with strange memory leak




Soulstorm
Nov 26, 2009, 03:33 AM
I had submitted an application called iMe for update in the application store. The review team rejected my application because of a strange bug that would only affect the application when ran using iPod touch, and not the iPhone. The problem regarded a crash that was taking place after the user took a photo from the Photo Library.

As I don't have an iPod touch, (I only have an iPhone), I ran the application using the simulator, and I noticed a strange memory double free, which I suspect it is the cause of the problem. Strange thing is that the output about a double free only appears on the simulator and not the actual iPhone!

The bug appears when taking pictures from the Library or from the Camera. I try to take a picture from the library, and then reduce it 2 times in a background thread, one for a medium sized image, and one as a thumbnail.

Here is some code of what I am doing.


- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissModalViewControllerAnimated:YES];
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
[self.takePictureButton setHidden:YES];
[self.selectFromCameraRollButton setHidden:YES];
[self performSelectorInBackground:@selector(saveOriginalImage:) withObject:image];
}

- (void)saveOriginalImage:(UIImage *)image
{
NSAutoreleasePool *externalPool = [[NSAutoreleasePool alloc]init];
SFGlobals *sharedGlobals = [SFGlobals sharedSFGlobals];
NSString *imagesDirectory = sharedGlobals.applicationImagesDirectory;
NSString *mediumImagesDir = sharedGlobals.applicationMediumSizedImagesDirectory;
NSString *fileID = [sharedGlobals generateImageID];


//UIImage *img = [UIImage imageWithContentsOfFile:[imagesDirectory stringByAppendingPathComponent:fileID]];
NSLog(@"saving original image...");

//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
[sharedGlobals saveImageAsJPGFile:image withQuality:1.0f withFileID:fileID toDirectory:sharedGlobals.applicationImagesDirectory];

UIImage *img = [[UIImage alloc]initWithContentsOfFile:[imagesDirectory stringByAppendingPathComponent:fileID]];
UIImage *mediumImage = [img scaledImage:428];

[sharedGlobals saveImageAsJPGFile:mediumImage withQuality:0.6f withFileID:fileID toDirectory:mediumImagesDir];

UIImage *smallImage = [mediumImage scaledImage:100];

[sharedGlobals saveImageAsJPGFile:smallImage withQuality:0.6f withFileID:fileID toDirectory:sharedGlobals.applicationThumbnailsDirectory];

[self finishUpImageProcessing:mediumImage withImageID:fileID];

[img release];
[externalPool release];
}

- (void)saveImageAsJPGFile:(UIImage *)image withQuality:(CGFloat)quality withFileID:(NSString *)fileID toDirectory:(NSString *)dir
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSLog(@"saving image to %@/%@", dir, fileID);
[UIImageJPEGRepresentation(image, quality) writeToFile:[dir stringByAppendingPathComponent:fileID] atomically:NO];
[pool drain];
}


//This is a function in a catefory that extends the UIImage class. It produces a shrinked
//image from the original image
- (UIImage *)scaledImage:(float)maxResolution
{
CGImageRef imgRef = self.CGImage;
float kMaxResolution = maxResolution;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);

if (width <= kMaxResolution && height <= kMaxResolution && self.imageOrientation == UIImageOrientationUp) {
return self;
}

CGAffineTransform transform = CGAffineTransformIdentity;
CGRect bounds = CGRectMake(0, 0, width, height);
if (width > kMaxResolution || height > kMaxResolution) {
CGFloat ratio = width/height;
if (ratio > 1) {
bounds.size.width = kMaxResolution;
bounds.size.height = bounds.size.width / ratio;
}
else {
bounds.size.height = kMaxResolution;
bounds.size.width = bounds.size.height * ratio;
}
}

CGFloat scaleRatio = bounds.size.width / width;
CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
CGFloat boundHeight;
switch(self.imageOrientation) {

case UIImageOrientationUp: //EXIF = 1
transform = CGAffineTransformIdentity;
break;

case UIImageOrientationUpMirrored: //EXIF = 2
transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
break;

case UIImageOrientationDown: //EXIF = 3
transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;

case UIImageOrientationDownMirrored: //EXIF = 4
transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
break;

case UIImageOrientationLeftMirrored: //EXIF = 5
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;

case UIImageOrientationLeft: //EXIF = 6
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;

case UIImageOrientationRightMirrored: //EXIF = 7
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeScale(-1.0, 1.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;

case UIImageOrientationRight: //EXIF = 8
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;

default:
[NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];

}

UIGraphicsBeginImageContext(bounds.size);

CGContextRef context = UIGraphicsGetCurrentContext();

if (self.imageOrientation == UIImageOrientationRight || self.imageOrientation == UIImageOrientationLeft) {
CGContextScaleCTM(context, -scaleRatio, scaleRatio);
CGContextTranslateCTM(context, -height, 0);
}
else {
CGContextScaleCTM(context, scaleRatio, -scaleRatio);
CGContextTranslateCTM(context, 0, -height);
}

CGContextConcatCTM(context, transform);

CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

//self.image = imageCopy;
return [imageCopy retain];
}

And here is my ouput from the console:

2009-11-26 11:19:32.793 iMe[896:5607] saving image to /Users/soulstorm/Library/Application Support/iPhone Simulator/User/Applications/B18D8CDB-05ED-4915-B0E5-4D0DDC136FBD/Documents/images/img21.jpg
2009-11-26 11:19:32.925 iMe[896:5607] saving image to /Users/soulstorm/Library/Application Support/iPhone Simulator/User/Applications/B18D8CDB-05ED-4915-B0E5-4D0DDC136FBD/Documents/mediums/img21.jpg
2009-11-26 11:19:32.944 iMe[896:5607] saving image to /Users/soulstorm/Library/Application Support/iPhone Simulator/User/Applications/B18D8CDB-05ED-4915-B0E5-4D0DDC136FBD/Documents/thumbnails/img21.jpg
iMe(896,0xb0103000) malloc: *** error for object 0x181c000: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
2009-11-26 11:19:32.947 iMe[896:5607] saving...
2009-11-26 11:19:32.973 iMe[896:5607] precaching image locations...

I believe that this malloc error is the cause of failure. Although I am not sure, I should try and eliminate that first, and then resubmit my application. Only I don't know exactly the cause of this error.

I would appreciate any help given regarding this matter.



Darkroom
Nov 26, 2009, 07:37 AM
don't trust the simulator, especially working with CG. i've experiences some crazy simulator+instruments bugs that do not appear when running the device with instruments.

do you get the same crash when running instruments with the device?

i've read several threads about apps that were refused by apple because of a crash that the developer could not simulate. in all cases they just resubmitted the same binary and it went thru.

it's good to double check, but i don't understand why there would be different reactions between devices using the same code to activate the photo library. if i were you i'd resubmit the same binary now. if you do find a bug then you can self-reject your binary and submit the fixed version.