memory leak in iPad app - objective-c

I've been working a product display app but it has a memory leak that causes it to crash after too many categories have been loaded up. The app works via a SplitViewController that lists the categories down the left and, once tapped, the product images show in the detailViewController on the right.
Selecting category after category eventually crashes the app.
I've used the instruments -> Leaks tool to trace the problem and I get told that NSString appendString is a leak. The number of strings leaked seems to match the number of products in the category selected so I'm guessing one of my loops holds the problem but, after playing around with AutoreleasePools I haven't solved it yet.
My code:
This method is called when the category is selected and parses an XML document
- (NSMutableArray*) processXML{
//NSAutoreleasePool *pool4 = [[NSAutoreleasePool alloc] init];
// Initialize the productEntries MutableArray declared in the header
products = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSMutableString *documentsDirectory = [[NSMutableString stringWithFormat:#"%#", [paths objectAtIndex: 0]] autorelease];
// paths to save inputs to
NSString *productsFile = [documentsDirectory stringByAppendingFormat: #"/products2.xml"];
NSData *data = [NSData dataWithContentsOfFile: productsFile];
// Create a new rssParser object based on the TouchXML "CXMLDocument" class, this is the object that actually grabs and processes the RSS data
NSError *error = nil;
CXMLDocument *rssParser = [[[CXMLDocument alloc] initWithData:(NSData *)data encoding:NSUTF8StringEncoding options:0 error:&error] autorelease];
// Create a new Array object to be used with the looping of the results from the rssParser
NSArray *resultNodes = NULL;
//NSString *xPathStart, *xPathEnd, *category, *finalStr;
NSString *xPathStart = [[NSString stringWithFormat:#""] autorelease];
NSString *xPathEnd = [[NSString stringWithFormat:#""] autorelease];
NSString *category = [[NSString stringWithFormat:#""] autorelease];
NSString *finalStr = [[NSString stringWithFormat:#""] autorelease];
NSString *detailStr = [[NSString stringWithFormat: detailItem] autorelease];
// category to be parsed - build up xPath expression
if([detailStr isEqualToString: #"On Order Stock"]) {
xPathStart = #"/products/product[instock='2";
xPathEnd = #"']";
finalStr = [NSString stringWithFormat:#"%#%#", xPathStart, xPathEnd];
} else {
xPathStart = #"/products/product[category='";
category = detailItem;
xPathEnd = #"']";
finalStr = [NSString stringWithFormat:#"%#%#%#", xPathStart, category, xPathEnd];
}
resultNodes = [rssParser nodesForXPath: finalStr error:nil];
// Loop through the resultNodes to access each items actual data
for (CXMLElement *resultElement in resultNodes) {
Product *productItem = [[Product alloc] init];
[productItem setCode: [[[resultElement childAtIndex: 1] stringValue] autorelease]];
[productItem setImage: [[[resultElement childAtIndex: 5] stringValue] autorelease]];
// Add the product object to the global productEntries Array so that the view can access it.
[products addObject: productItem];
[productItem release];
}
//[pool4 release];
return products;
}
As you can see I went a little crazy with autoReealse on my strings. The other code segment that displays the images could be the problem although Leaks does mention processXML directly.
- (void) displayImages:(NSMutableArray *)anArray {
// create scrollView object
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 100)];
scrollView.pagingEnabled = NO;
scrollView.scrollEnabled = YES;
scrollView.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1];
scrollView.userInteractionEnabled = YES;
//create info area below scrollView
infoView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 100, self.view.frame.size.width, 100)];
[infoView setContentSize:CGSizeMake(self.view.frame.size.width, 100)];
infoView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
infoView.scrollEnabled = NO;
[barcodeImgView setImage:[UIImage imageNamed:#"barcode2.jpg"]];
[infoView addSubview:codeLbl];
[infoView addSubview:nameLbl];
[infoView addSubview:priceLbl];
[infoView addSubview:dimensionsLbl];
[infoView addSubview:stockLbl];
[infoView addSubview:commentsLbl];
[infoView addSubview:barcodeImgView];
infoView.userInteractionEnabled = YES;
[codeLbl setText:[[NSString stringWithFormat:#""] autorelease]];
[nameLbl setText:[[NSString stringWithFormat:#""] autorelease]];
[priceLbl setText:[[NSString stringWithFormat:#""] autorelease]];
[commentsLbl setText:[[NSString stringWithFormat:#""] autorelease]];
[stockLbl setText:[[NSString stringWithFormat:#""] autorelease]];
[dimensionsLbl setText:[[NSString stringWithFormat:#""] autorelease]];
// hold x and y of each image
int x = 30;
int y = 50;
int noOfImages = [anArray count];
int maxRowWidth = (noOfImages / 3) + 1;
int xcount = 0; // position across the row, reset to zero and drop image down when equal to (noOfImages / 3) + 1
//NSAutoreleasePool *displayPool = [[NSAutoreleasePool alloc] init];
for(int i = 0; i < noOfImages; i++) {
// declare Product object to hold items in anArray
Product *prod = [[Product alloc] init];
prod = [anArray objectAtIndex: i];
// try for image in Documents folder, later checks it exists and if not uses Resource location
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSMutableString *documentsDirectory = [[NSString stringWithFormat:#"%#", [paths objectAtIndex: 0]] autorelease];;
// paths to save inputs to
NSString *imgName = [[NSString stringWithFormat:#"%#/%#", documentsDirectory, [prod image]] autorelease];
NSString *productName = [[NSString stringWithFormat:#"%#", [prod code]] autorelease];
// create and size image
UIImage *image = [UIImage imageWithContentsOfFile: imgName];
// set up button
UIButton *button= [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(imageButtonClick:) forControlEvents:(UIControlEvents)UIControlEventTouchDown];
[button setTitle:productName forState:UIControlStateNormal];
button.titleLabel.font = [UIFont systemFontOfSize: 0];
[button setTitleColor: [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1] forState: UIControlStateNormal];
CGSize imageSize = image.size;
CGFloat height = imageSize.height;
CGFloat width = imageSize.width;
CGFloat ratio = 160 / width; // get ratio to divide height by
UIGraphicsBeginImageContext(CGSizeMake((height * ratio),160));
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect: CGRectMake(0, 0, height * ratio, 160)];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// create frame for image
CGRect newFrame = CGRectMake(x, y, 160,160);
UILabel *codeLabel = [[UILabel alloc] initWithFrame:CGRectMake(x, y - 20, 170, 20)];
codeLabel.text = productName;
codeLabel.textColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
codeLabel.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1];
[button setFrame: newFrame];
[button setBackgroundImage:smallImage forState:UIControlStateNormal];
[scrollView setContentSize:CGSizeMake((maxRowWidth * 160) + 160,self.view.frame.size.height - 100)];
[self.scrollView addSubview:button];
[self.scrollView addSubview:codeLabel];
xcount++;
x = x + 170; // move across the page
if(xcount == maxRowWidth) {
y = y + 210; // move down the screen for the next row
x = 30; // reset x to left of screen
xcount = 0; // reset xcount;
}
[prod release];
}
//[displayPool release];
[self.view addSubview: scrollView];
[self.view addSubview: infoView];
[scrollView release];
[infoView release];
[pool release];
}
By the way, pool is an autoreleasePool defined in the h file for the class.
I would really appreciate any specific help regarding my code or general tips on what could be wrong.

I see a few things wrong:
As was mentioned in the comments, you're abusing -autorelease in ways which make grown men cry and that which will crash your app.
-processXML is returning an owned object. You're allocating products and returning it. This breaks convention, because the method name does not begin with new or alloc, and does not contain copy. You should return [products autorelease]; instead. However, even that is shady, because since products isn't declared locally, it's probably an instance variable. In that case, what happens if processXML gets invoked multiple times? You have an owned object referenced by the instance variable, and suddenly you overwrite that reference with a new one... = memory leak.
Every time someone does MyClass * object = [[MyClass alloc] init]; object = [something thatReturnsAMyClass];, a kitten dies. If you then do [object release];, a second dies for good measure. This is a terrible, terrible memory leak (and a likely crash). You're allocating a new object and then immediately throwing it away but never releasing it. That you do this suggests you don't really get what a pointer is. I suggest reading "Everything you need to know about pointers in C"
On a lighter note, you should check out -[NSString stringByAppendingPathComponent:]. NSString has a bunch of really nice methods for dealing with paths.
I hope I don't come off as too harsh. :)

Some while ago at another post somebody said that one should read about the memory management and I was actually thinking that this answer is not really right. What is wrong with some trial and error and learning by doing. But after I had my painful experiences with memory I must admit that this guy was right. Take the time. Go and read the chapter on memory management in the apple documentation.
As above stated already you should not autorelease an object you do not own. But this might not cause the trouble. You can next to the instruments use Build+Analyze in the Build menu. This will help you to find out more.
Basically you need to release objects you create which you own (which one you own is in the documentation, basically those created with "alloc" and some more). If you cannot release them you assign them to the autorelease pool. This is the case with the "products" you return from processXML. When is the autorelease pool drained? This is when the next time the framework of the application is back in control (I think it was called run-loop or something). This can be a while and so you should not open to much objects which are assigned to an autorelease pool.
So to help you really read that chapter: memory management programming guide

Related

custom Annotations being switched when reloaded on MKMapView

I've been having this issue for a couple of weeks now, and I still have not found an answer. on my MapView I have custom annotations, and when I hit the "reload button" all the information is correct as in the annotation "title, subtitle". but the annotation has changed. the annotations are in a NSMutableArray and I'm sure that the issue i am having revolves around that. here is the code I am using to reload the annotations.
so just prevent any confusion, my custom annotations work just fine when i first load the mapView. But once i hit the reload button, all the annotation's information like "location,title, subtitle" all that is correct, just the actual annotation has changed. Like all the annotations have been switched around.
if anyone can help, it would greatly be appreciated! thanks!
- (IBAction)refreshMap:(id)sender {
NSArray *annotationsOnMap = myMapView.annotations;
[myMapView removeAnnotations:annotationsOnMap];
[locations removeAllObjects];
[citiesArray removeAllObjects];
[self retrieveData];
}
-(void) retrieveData {
userLAT = [NSString stringWithFormat:#"%f", myMapView.userLocation.coordinate.latitude];
userLNG = [NSString stringWithFormat:#"%f", myMapView.userLocation.coordinate.longitude];
NSString *fullPath = [mainUrl stringByAppendingFormat:#"map_json.php?userID=%#&lat=%#&lng=%#",theUserID,userLAT,userLNG];
NSURL * url =[NSURL URLWithString:fullPath];
NSData *data = [NSData dataWithContentsOfURL:url];
json =[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
citiesArray =[[NSMutableArray alloc]init];
for (int i = 0; i < json.count; i++)
{
//create city object
NSString * eID =[[json objectAtIndex:i] objectForKey:#"userid"];
NSString * eAddress =[[json objectAtIndex:i] objectForKey:#"full_address"];
NSString * eHost =[[json objectAtIndex:i] objectForKey:#"username"];
NSString * eLat =[[json objectAtIndex:i] objectForKey:#"lat"];
NSString * eLong =[[json objectAtIndex:i] objectForKey:#"lng"];
NSString * eName =[[json objectAtIndex:i] objectForKey:#"Restaurant_name"];
NSString * eState = [[json objectAtIndex:i] objectForKey:#"type"];
NSString * annotationPic = [[json objectAtIndex:i] objectForKey:#"Annotation"];
NSString * eventID = [[json objectAtIndex:i] objectForKey:#"id"];
//convert lat and long from strings
float floatLat = [eLat floatValue];
float floatLONG = [eLong floatValue];
City * myCity =[[City alloc] initWithRestaurantID: (NSString *) eID andRestaurantName: (NSString *) eName andRestaurantState: (NSString *) eState andRestaurantAddress: (NSString *) eAddress andRestaurantHost: eHost andRestaurantLat: (NSString *) eLat andRestaurantLong: (NSString *) eLong];
//Add our city object to our cities array
// Do any additional setup after loading the view.
[citiesArray addObject:myCity];
//Annotation
locations =[[NSMutableArray alloc]init];
CLLocationCoordinate2D location;
Annotation * myAnn;
//event1 annotation
myAnn =[[Annotation alloc]init];
location.latitude = floatLat;
location.longitude = floatLONG;
myAnn.coordinate = location;
myAnn.title = eName;
myAnn.subtitle = eHost;
myAnn.type = eState;
myAnn.AnnotationPicture = annotationPic;
myAnn.passEventID = eventID;
myAnn.hotZoneLevel = hotZone;
[locations addObject:myAnn];
[self.myMapView addAnnotations:locations];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *annotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *annotationView = (MKAnnotationView *) [self.myMapView
dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (!annotationView)
{
annotationView = [[MKAnnotationView alloc]
initWithAnnotation:annotation
reuseIdentifier:annotationIdentifier];
NSString *restaurant_Icon = ((Annotation *)annotation).AnnotationPicture;
NSString *restaurant_Callout = [NSString stringWithFormat:#"mini.%#",restaurant_Icon];
UIImage *oldImage = [UIImage imageNamed:restaurant_Icon];
UIImage *newImage;
CGSize newSize = CGSizeMake(75, 75);
newImage = [oldImage imageScaledToFitSize:newSize]; // uses MGImageResizeScale
annotationView.image= newImage;
annotationView.canShowCallout = YES;
UIImage *Mini_oldImage = [UIImage imageNamed:event_Callout];
UIImage *Mini_newImage;
CGSize Mini_newSize = CGSizeMake(30,30);
Mini_newImage = [Mini_oldImage imageScaledToFitSize:Mini_newSize]; // uses MGImageResizeScale
UIImageView *finalMini_callOut = [[UIImageView alloc] initWithImage:Mini_newImage];
annotationView.leftCalloutAccessoryView = finalMini_callOut;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = rightButton;
}
else
{
annotationView.annotation = annotation;
}
return annotationView;
}
If nothing else, you're setting the icon and the callout based upon the annotation, but only doing that in viewForAnnotation if the annotation was not dequeued. You really want to do any annotation-specific customization outside of that if block, in case an annotation view is reused.
Unrelated to your reported issue, there are a few other observations:
You probably should be doing retrieveData asynchronously so you don't tie up the main thread with your data retrieval/parsing. Go ahead and dispatch the adding of the entry to your array and adding the annotation to the map in the main queue, but the network stuff and should definitely be done asynchronously.
You probably should check to make sure data is not nil (e.g. no network connection or some other network error) because JSONObjectWithData will crash if you pass it a nil value.
Your use of locations seems unnecessary because you're resetting it for every entry in your JSON. You could either (a) retire locations entirely and just add the myAnn object to your map's annotations; or (b) initialize locations before the for loop. But it's probably misleading to maintain this ivar, but only populate it with the last annotation.

Creating an animated GIF in Cocoa - defining frame type

I've been able to adapt some code found on SO to produce an animated GIF from the "screenshots" of my view, but the results are unpredictable. GIF frames are sometimes full images, full frames ("replace" mode, as GIMP marks it), other times are just a "diff" from previous layer ("combine" mode).
From what I've seen, when there are fewer and/or smaller frames involved, the CG writes the GIF in "combine" mode, but failing to get the colors right. Actually, the moving parts are colored correctly, the background is wrong.
When CG saves the GIF as full frames, the colors are ok. The file size is larger, but hey, obviously you cannot have the best of both worlds. :)
Is there a way to either:
a) force CG to create "full frames" when saving the GIF
b) fix the colors (color table?)
What I do is (ARC mode):
capture the visible part of the view with
[[scrollView contentView] dataWithPDFInsideRect:[[scrollView contentView] visibleRect]];
convert and resize it to NSImageBitmapRep of PNG type
-(NSMutableDictionary*) pngImageProps:(int)quality {
NSMutableDictionary *pngImageProps;
pngImageProps = [[NSMutableDictionary alloc] init];
[pngImageProps setValue:[NSNumber numberWithBool:NO] forKey:NSImageInterlaced];
double compressionF = 1;
[pngImageProps setValue:[NSNumber numberWithFloat:compressionF] forKey:NSImageCompressionFactor];
return pngImageProps;
}
-(NSData*) resizeImageToData:(NSData*)data toDimX:(int)xdim andDimY:(int)ydim withQuality:(int)quality{
NSImage *image = [[NSImage alloc] initWithData:data];
NSRect inRect = NSZeroRect;
inRect.size = [image size];
NSRect outRect = NSMakeRect(0, 0, xdim, ydim);
NSImage *outImage = [[NSImage alloc] initWithSize:outRect.size];
[outImage lockFocus];
[image drawInRect:outRect fromRect:inRect operation:NSCompositeCopy fraction:1];
NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:outRect];
[outImage unlockFocus];
NSMutableDictionary *imageProps = [self pngImageProps:quality];
NSData* imageData = [bitmapRep representationUsingType:NSPNGFileType properties:imageProps];
return [imageData copy];
}
get the array of BitmapReps and create the GIF
-(CGImageRef) pngRepDataToCgImageRef:(NSData*)data {
CFDataRef imgData = (__bridge CFDataRef)data;
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData (imgData);
CGImageRef image = CGImageCreateWithPNGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
return image;
}
////////// create GIF from
NSArray *images; // holds all BitmapReps
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:pot],
kUTTypeGIF,
allImages,
NULL);
// set frame delay
NSDictionary *frameProperties = [NSDictionary
dictionaryWithObject:[NSDictionary
dictionaryWithObject:[NSNumber numberWithFloat:0.2f]
forKey:(NSString *) kCGImagePropertyGIFDelayTime]
forKey:(NSString *) kCGImagePropertyGIFDictionary];
// set gif color properties
NSMutableDictionary *gifPropsDict = [[NSMutableDictionary alloc] init];
[gifPropsDict setObject:(NSString *)kCGImagePropertyColorModelRGB forKey:(NSString *)kCGImagePropertyColorModel];
[gifPropsDict setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCGImagePropertyGIFHasGlobalColorMap];
// set gif loop
NSDictionary *gifProperties = [NSDictionary
dictionaryWithObject:gifPropsDict
forKey:(NSString *) kCGImagePropertyGIFDictionary];
// loop through frames and add them to GIF
for (int i=0; i < [images count]; i++) {
NSData *imageData = [images objectAtIndex:i];
CGImageRef imageRef = [self pngRepDataToCgImageRef:imageData];
CGImageDestinationAddImage(destination, imageRef, (__bridge CFDictionaryRef) (frameProperties));
}
// save the GIF
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)(gifProperties));
CGImageDestinationFinalize(destination);
CFRelease(destination);
I've checked the ImageBitmapReps, when saved as PNG individually, they are just fine.
As I understood, the color tables should be handled by CG or am I responsible to produce the dithered colors? How to do that?
Even when doing the same animation repeatedly, the GIFs produced may vary.
This is a single BitmapRep
(source: andraz.eu)
And this is the GIF with the invalid colors ("combine" mode)
(source: andraz.eu)
I read your code. Please double check the "allImages" while you are creating the CGImageDestinationRef, and the "[images count]".
the follow test code works fine:
NSDictionary *prep = [NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.2f] forKey:(NSString *) kCGImagePropertyGIFDelayTime] forKey:(NSString *) kCGImagePropertyGIFDictionary];
CGImageDestinationRef dst = CGImageDestinationCreateWithURL((__bridge CFURLRef)(fileURL), kUTTypeGIF, [filesArray count], nil);
for (int i=0;i<[filesArray count];i++)
{
//load anImage from array
...
CGImageRef imageRef=[anImage CGImageForProposedRect:nil context:nil hints:nil];
CGImageDestinationAddImage(dst, imageRef,(__bridge CFDictionaryRef)(prep));
}
bool fileSave = CGImageDestinationFinalize(dst);
CFRelease(dst);

Array with UIImageView elements - setImageWithURL

I have such code
arrayWithImages = [[NSMutableArray alloc] init];
NSEnumerator *enumForNames = [arrayWithNames objectEnumerator];
NSEnumerator *enumForURLs = [arrayWithURLs objectEnumerator];
id objName, objURL;
while(objName = [enumForNames nextObject]) {
objURL = [enumForURLs nextObject];
UIImageView *anImage = nil;
[anImage setImageWithURL:[NSURL URLWithString:objURL]];
(...)
[arrayWithImages addObject:anImage];
}
And each time I got SIGABRT in line with "[arrayWithImages addObject:anImage];"
What's here wrong?
I don’t see an setImageWithURL method on UIImageView. Where is this from?
Is there any output from the SIGABRT crash?
Try this code:
// Download the image
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:objURL]];
// Make an UIImage out of it
UIImage *anImage = [UIImage imageWithData:imageData];
// Create an image view with the image
UIImageView *imageView = [[UIImageView alloc] initWithImage:anImage];
// Check to make sure it exists
if (imageView != nil) {
// Add it to your array
[arrayWithImages addObject:imageView];
else {
NSLog(#"Image view is nil");
}
Note that you should download the images asynchronously to avoid hanging the loop. This blog post discussing asynchronous image downloading.
Also if you know that [enumForURLs nextObject]; will return an NSString (or even better, NSURL) you should assign objURL to type NSString (or NSURL).

issue in generating image files in mac os x app cocoa

-(void)processImage:(NSString*)inputPath:(int)imageWidth:(int)imageHeight:(NSString*)outputPath {
// NSImage * img = [NSImage imageNamed:inputPath];
NSImage *image = [[NSImage alloc] initWithContentsOfFile:inputPath];
[image setSize: NSMakeSize(imageWidth,imageHeight)];
[[image TIFFRepresentation] writeToFile:outputPath atomically:NO];
NSLog(#"image file created");
}
- (IBAction)processImage:(id)sender {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSNumber *timeStampObj = [NSNumber numberWithInt:timeStamp];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterNoStyle];
NSString *convertNumber = [formatter stringForObjectValue:timeStampObj];
NSLog(#"timeStampObj:: %#", convertNumber);
fileNameNumber = [[convertNumber stringByAppendingString:[self genRandStringLength:8]] retain];
int i; // Loop counter.
// Loop through all the files and process them.
for( i = 0; i < [files count]; i++ )
{
inputFilePath = [[files objectAtIndex:i] retain];
NSLog(#"filename::: %#", inputFilePath);
// Do something with the filename.
[selectedFile setStringValue:inputFilePath];
NSLog(#"selectedFile:::: %#", selectedFile);
}
NSLog(#"curdir:::::%#", inputFilePath);
NSString *aString = [[NSString stringWithFormat:#"%#%#%#", thumbnailDirPath , #"/" , fileNameNumber] retain];
fileNameJPG = [[aString stringByAppendingString:#"_small.jpg"] retain];
fileNameJPG1 = [[aString stringByAppendingString:#".jpg"] retain];
fileNameJPG2 = [[aString stringByAppendingString:#"_H.jpg"] retain];
[self processImage:inputFilePath: 66 :55 :fileNameJPG];
[self processImage:inputFilePath: 800 :600 :fileNameJPG1];
[self processImage:inputFilePath: 320 :240 :fileNameJPG2];
}
The issue I am facing is that the above code is generating 3 files with different names(as I have defined the name should be) having the same size of all 3 files but not with the dimensions or width/length I pass to the function.
What can be the issue ?
NSImage objects are immutable. So image is not modified when you change its size.
You should use something like the following code (adapted from here).
-(void)saveImageAtPath:(NSString*)sourcePath toPath:(NSString*)targetPath withWidth:(int)targetWidth andHeight:(int)targetHeight
{
NSImage *sourceImage = [[NSImage alloc] initWithContentsOfFile:sourcePath];
NSImage *targetImage = [[NSImage alloc] initWithSize: NSMakeSize(targetWidth, targetHeight)];
NSSize sourceSize = [sourceImage size];
NSRect sourceRect = NSMakeRect(0, 0, sourceSize.width, sourceSize.height);
NSRect targetRect = NSMakeRect(0, 0, targetWidth, targetWidth);
[targetImage lockFocus];
[sourceImage drawInRect:targetRect fromRect:sourceRect operation: NSCompositeSourceOver fraction: 1.0];
[targetImage unlockFocus];
[[targetImage TIFFRepresentation] writeToFile:targetPath atomically:NO];
NSLog(#"image file created");
[sourceImage release];
[targetImage release];
}

Stumped on a memory leak for OS X app

OK, I have a memory management problem that is driving me up a wall. At one point I swear this worked with no problems, but now it's leaking memory everywhere and I can't figure out why.
To begin with I'm starting an NSTask and then running a loop while the task is running.
NSTask *encodingTask = [[NSTask alloc] init];
NSFileHandle *taskStdout = [NSFileHandle fileHandleForWritingAtPath:encodingOutput];
[encodingTask setStandardOutput:taskStdout];
[encodingTask setStandardError:taskStdout];
NSString argString = [NSString stingWithString: #"some arguments"];
[encodingTask setArguments:taskArgs];
[encodingTask setLaunchPath:somePath];
[encodingTask launch];
while ([encodingTask isRunning]){
sleep(1);
[self encodeProgressTimer];
}
The encodeProgessTimer method is grabbing the last line from the stdOut and placing that in the menu bar:
- (void)encodeProgressTimer
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"menuProgress"]) {
// Read the last line
NSString *fileData = [NSString stringWithContentsOfFile:encodingOutput encoding:NSASCIIStringEncoding error:nil];
NSArray *lines = [fileData componentsSeparatedByString:#"\r"];
NSString *lastLine = [lines objectAtIndex:[lines count] - 1];
NSString *percent;
NSString *eta;
BOOL dataFound = NO;
if ([lastLine length] == 71) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {61,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
else if ([lastLine length] == 72) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {62,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
else if ([lastLine length] == 70) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {60,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
if (dataFound) {
NSMutableString *bottomStr = [[NSMutableString alloc]
initWithFormat:#"Encoding: %#%% - ETA %#", percent, eta];
[appDelegate setMenuTop:topString andBottom:bottomStr];
[bottomStr release];
}
}
}
It's my understanding that anything I'm not specifically allocating and initializing should be auto released when the method has completed, but that isn't the case. Memory usage goes up exponentially every second when this is called. If I look at my memory allocations the number of living CFstings goes through the roof. If I turn of encodeProgressTimer my problems go away. I tried adding an autorelease pool to encodeProgressTimer which made memory usage very stable, however after 20 minutes or so of running I get a EXC_BAD_ACCESS. Turning on Zombies turns that into:
*** -[NSConcreteAttributedString _drawCenteredVerticallyInRect:scrollable:]: message sent to deallocated instance 0x2bc756e0
I actually went through and changed each variable declaration into it's alloc/init counterpart and manually released them, but that didn't solve the problem either. At this point I'm pretty stumped.
Also for the sake of completeness the [appDelegate setMenuTop: andBottom:] method looks like this:
-(void) setMenuTop: (NSString *) top andBottom: (NSString *) bottom
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"menuProgress"]) {
[statusItem setImage:nil];
NSMutableParagraphStyle *lineHeight = [[NSMutableParagraphStyle alloc] init];
[lineHeight setMaximumLineHeight:10.5];
[lineHeight setLineBreakMode:NSLineBreakByTruncatingMiddle];
OperationQueue *opQueue = [OperationQueue sharedQueue];
NSString *sBuffer = [[NSMutableString alloc] initWithFormat: #"%# (%i More)\n%#", top, [opQueue queueCount] - 1, bottom];
attributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont menuFontOfSize:9], NSFontAttributeName, lineHeight, NSParagraphStyleAttributeName, nil];
if (statusTitle)
[statusTitle release];
statusTitle = [[NSAttributedString alloc] initWithString: sBuffer attributes: attributes];
[statusItem setAttributedTitle: statusTitle];
[lineHeight release];
[sBuffer release];
[attributes release];
}
}
There will be loads of stuff in the autorelease pool, but you need to explicitly drain it for the memory to go away. Change your while loop as follows:
while ([encodingTask isRunning]){
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
sleep(1);
[self encodeProgressTimer];
[pool drain];
}
Other stuff: if you are running this on a thread, you can't update user interface items directly. You need to use something like performSelectorOnMainThread: to actualy update the UI. If you are not running this on a thread, you need to rethink your design. The whole UI of your application will freeze while the loop is running.
You may want to use properties here or nil out the reference.
if (statusTitle) {
[statusTitle release];
statusTitle = nil;
}