iPhone: Memory leak when using NSOperationQueue - iphone

I'm sitting here for at least half an hour to find a memory leak in my code.
I just replaced an synchronous call to a (touch) method with an asynchronous one using NSOperationQueue.
The Leak Inspector reports a memory leak after I did the change to the code.
What's wrong with the version using NSOperationQueue?
Version without a MemoryLeak
-(NSData *)dataForKey:(NSString*)ressourceId_
{
NSString *cacheKey = [self cacheKeyForRessource:ressourceId_]; // returns an autoreleased NSString*
NSString *path = [self cachePathForKey:cacheKey]; // returns an autoreleased NSString*
NSData *data = [[self memoryCache] objectForKey:cacheKey];
if (!data)
{
data = [self loadData:path]; // returns an autoreleased NSData*
if (data)
{
[[self memoryCache] setObject:data forKey:cacheKey];
}
}
[[self touch:path];
return data;
}
Version with a MemoryLeak (I do not see any)
-(NSData *)dataForKey:(NSString*)ressourceId_
{
NSString *cacheKey = [self cacheKeyForRessource:ressourceId_]; // returns an autoreleased NSString*
NSString *path = [self cachePathForKey:cacheKey]; // returns an autoreleased NSString*
NSData *data = [[self memoryCache] objectForKey:cacheKey];
if (!data)
{
data = [self loadData:path]; // returns an autoreleased NSData*
if (data)
{
[[self memoryCache] setObject:data forKey:cacheKey];
}
}
NSInvocationOperation *touchOp = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(touch:) object:path];
[[self operationQueue] addOperation:touchOp];
[touchOp release];
return data;
}
And of course, the touch method does nothing special too. Just change the date of the file.
-(void)touch:(id)path_
{
NSString *path = (NSString *)path_;
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:path])
{
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSDate date], NSFileModificationDate, nil];
[fm setAttributes: attributes ofItemAtPath:path error:nil];
}
}

Related

NSCache evicting objects on setObjectForKey

NSCache is evicting objects everytime I set one! This is nuts.
Here is how I'm adding objects:
[self.inMemoryCache setObject:#{#"data" : data, #"filePath" : filePath} forKey:filePath];
Here is my delegate method willEvictObject:
-(void)cache:(NSCache *)cache willEvictObject:(id)obj
{
NSLog(#"evicted obj %#",[obj description]);
if ([obj isKindOfClass:[NSDictionary class]]) {
NSData *data = [obj objectForKey:#"data"];
NSString *filePath = [obj objectForKey:#"filePath"];
[data writeToFile:filePath atomically:YES];
}else if ([obj isKindOfClass:[NSArray class]]){
NSArray *viewControllerNames = obj;
[viewControllerNames writeToFile:[UINavigationController pathToData] atomically:YES];
}
}
Every time I add object to the cache it evicts it. Really strange.
Any ideas? Here is how I'm allocating it:
navigationController.inMemoryCache = [[NSCache alloc] init];
navigationController.inMemoryCache.delegate = navigationController;
Thanks!

AFGetImageOperation in OpenFlow

What's the correct way to implement AFGetImageOperation for OpenFlow.
AFGetImageOperation *getImageOperation = [[AFGetImageOperation alloc] initWithIndex:i viewController:self];
getImageOperation.imageURL = [NSURL URLWithString:aImage.imageURL];
[loadImagesOperationQueue addOperation:getImageOperation];
[getImageOperation release];
aImage.imageURL has the requested image URL but unsure where the retrieved image is stored?
Thanks
Images are not cached. It fetches the image again and again.
You can cache the image using following methods..
-(NSString *) md5String
{
NSString *md5 = [Utilities md5String:[imageURL absoluteString]];
return md5;
}
-(void) storeImage:(UIImage *)image AtPath:(NSString *)path
{
NSFileManager *manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:path])
{
[manager removeItemAtPath:path error:nil];
}
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:NO];
}
//TODO: //We need to cehck the expiry date as well..
//-(UIImage *) imageFromPath:(NSString *)path Expiry:()
-(UIImage *) loadImageFromPath:(NSString *)path
{
UIImage *image = nil;
NSFileManager *manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:path])
{
image = [[[UIImage alloc] initWithContentsOfFile:path] autorelease];
}
return image;
}
-(NSString *) cachedImagePath
{
NSString *md5 = [self md5String];
NSString *cachedFilePath = [[Utilities applicationCacheDirectory] stringByAppendingPathComponent:md5];
return cachedFilePath;
}
- (void)main {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
if (imageURL) {
UIImage *photo = nil;
NSString *cachedFilePath = [self cachedImagePath];
UIImage *image = [self loadImageFromPath:cachedFilePath];
if(image)
{
photo = image;
}
else
{
NSData *photoData = [NSData dataWithContentsOfURL:imageURL];
photo = [UIImage imageWithData:photoData];
[self storeImage:photo AtPath:cachedFilePath];
}
// Create a UIImage from the imageURL.
if (photo) {
[mainViewController performSelectorOnMainThread:#selector(imageDidLoad:)
withObject:[NSArray arrayWithObjects:photo, [NSNumber numberWithInt:photoIndex], nil]
waitUntilDone:YES];
}
} else {
// Load an image named photoIndex.jpg from our Resources.
NSString *imageName = [[NSString alloc] initWithFormat:#"place_holder_bg.png", photoIndex];
UIImage *theImage = [UIImage imageNamed:imageName];
if (theImage) {
[mainViewController performSelectorOnMainThread:#selector(imageDidLoad:)
withObject:[NSArray arrayWithObjects:theImage, [NSNumber numberWithInt:photoIndex], nil]
waitUntilDone:YES];
} else
NSLog(#"Unable to find sample image: %#", imageName);
[imageName release];
}
[pool release];
}

Memory Leak related

i am working on a fishing app these days and i am getting a memory leak problem
-(void)requestFinished:(ASIFormDataRequest *) request {
if(hud != nil){
[hud show:NO];
[hud release];
hud = nil;
}
isLoading = NO;
self.responseText = [request responseString];
[self parseXml]; //I am getting leak here
if ( [self.responseText hasPrefix:#"<result>"]) {
UIAlertView *info = [[[UIAlertView alloc] initWithTitle:#" " message:#"Limited Internet access, please find a stronger signal in the area" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil]autorelease];
[info show];
}
if (!isRefreshButtonClicked) {
[UIAccelerometer sharedAccelerometer].delegate = self;
[NSThread detachNewThreadSelector:#selector(parseXml) toTarget:self withObject:nil];
} }
This is my function...
-(void) parseXml
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
_fishes = [[fishes parseXml:self.responseText] retain];
[self performSelectorOnMainThread:#selector(parseXmlDone) withObject:nil waitUntilDone:YES];
[pool release];
Here _fishes is an array which is getting a value from a array return type function.....and here is that function...
+(NSMutableArray *)parseXml:(NSString *)xmlString {
//xmlString = [xmlString stringByReplacingOccurrencesOfString:#"&" withString:#""];
const char *cString = [xmlString UTF8String];
NSMutableArray *fishes = [NSMutableArray array];
NSData *xmlData = [NSData dataWithBytes:cString length:strlen(cString)];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc]initWithData:xmlData options:0 error:&error];
if (doc == nil) { return nil; }
//parseXml
NSArray *_fishes = [doc.rootElement elementsForName:#"fishery"];
for (GDataXMLElement *_fish in _fishes) {
NSMutableDictionary *fish = [NSMutableDictionary dictionary];
NSArray *ids = [_fish elementsForName:#"id"];
if ([ids count]>0) {
GDataXMLElement *firstId = (GDataXMLElement *)[ids objectAtIndex:0];
[fish setValue:firstId.stringValue forKey:#"id"];
} else continue;
NSArray *names = [_fish elementsForName:#"name"];
if ([names count]>0) {
GDataXMLElement *firstName = (GDataXMLElement *)[names objectAtIndex:0];
[fish setValue:firstName.stringValue forKey:#"name"];...........
........
else continue;
NSArray *distances = [_fish elementsForName:#"distance"];
if ([distances count]>0) {
GDataXMLElement *distance = (GDataXMLElement *)[distances objectAtIndex:0];
[fish setValue:distance.stringValue forKey:#"distance"];
}else continue;
[fishes addObject:fish];
}
[doc release];
return fishes;
}
#end
I hope u guys will understand my problem...thanx
In -parseXml,
_fishes = [[fishes parseXml:self.responseText] retain];
will leak any previous object _fishes was pointing to in case -parseXml is sent more than once. You could use a retain property instead of an instance variable, or a setter method that releases the previous object, or release the previous object before assigning a new (retained) object to _fishes.

Reading UIImage from Document folder

I need to read the image from document folder and prepare UIImage out of that. but when I call this function everytime memory get increasing and for large scale image this scale is large. following is same code
-(UIImage*) GetSavedImageWithName:(NSString*) aFileName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableString* str = [[NSMutableString alloc] initWithCapacity:300];
[str appendString:documentsDirectory];
[str appendString:#"/"];
[str appendString:aFileName];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success = [fileManager fileExistsAtPath:str];
NSData *dataToWrite = nil;
UIImage* image = nil;
if(!success)
{
}
else
{
dataToWrite = [[NSData alloc] initWithContentsOfFile:str];
image = [UIImage imageWithData:dataToWrite]; YES;
}
if(dataToWrite)
{
[dataToWrite release];
dataToWrite = nil;
}
if(str)
{
[str release];
str = nil;
}
if(fileManager)
{
[fileManager release];
fileManager = nil;
}
return image;
}
Calling this way
[self SetFrontViewImage:[self GetSavedImageWithName:#"Edited"]];
I am not able to find where it is getting leak.
Thanks,
Sagar
It's consuming more memory because you're reading file-converting it to data-converting it to image and again using class method.
So instead use following way:
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success = [fileManager fileExistsAtPath:str];
UIImage* image = nil;
if(!success)
{
return nil;
}
else
{
image = [[UIImage alloc] initWithContentsOfFile:str]; YES;
}
return [image autorelease];
}
And don't release your file manager object. It is an auto release object.
Because the returned UIImage is an autorelease object. It means the run loop will release these autorelease objects later. Before it release those autorelease objects, the memory space will grow up, especially when loading images with loop or a work thread.

Find out when all processes in (void) is done?

I need to know how you can find out when all processes (loaded) from a - (void) are done, if it's possible.
Why? I'm loading in data for a UITableView, and I need to know when a Loading... view can be replaced with the UITableView, and when I can start creating the cells.
This is my code:
- (void) reloadData {
NSAutoreleasePool *releasePool = [[NSAutoreleasePool alloc] init];
NSLog(#"Reloading data.");
NSURL *urlPosts = [NSURL URLWithString:[NSString stringWithFormat:#"%#", URL]];
NSError *lookupError = nil;
NSString *data = [[NSString alloc] initWithContentsOfURL:urlPosts encoding:NSUTF8StringEncoding error:&lookupError];
postsData = [data componentsSeparatedByString:#"~"];
[data release], data = nil;
urlPosts = nil;
self.numberOfPosts = [[postsData objectAtIndex:0] intValue];
self.postsArrayID = [[postsData objectAtIndex:1] componentsSeparatedByString:#"#"];
self.postsArrayDate = [[postsData objectAtIndex:2] componentsSeparatedByString:#"#"];
self.postsArrayTitle = [[postsData objectAtIndex:3] componentsSeparatedByString:#"#"];
self.postsArrayComments = [[postsData objectAtIndex:4] componentsSeparatedByString:#"#"];
self.postsArrayImgSrc = [[postsData objectAtIndex:5] componentsSeparatedByString:#"#"];
NSMutableArray *writeToPlist = [NSMutableArray array];
NSMutableArray *writeToNoImagePlist = [NSMutableArray array];
NSMutableArray *imagesStored = [NSMutableArray arrayWithContentsOfFile:[rootPath stringByAppendingPathComponent:#"imagesStored.plist"]];
int loop = 0;
for (NSString *postID in postsArrayID) {
if ([imagesStored containsObject:[NSString stringWithFormat:#"%#.png", postID]]){
NSLog(#"Allready stored, jump to next. ID: %#", postID);
continue;
}
NSLog(#"%#.png", postID);
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[postsArrayImgSrc objectAtIndex:loop]]];
// If image contains anything, set cellImage to image. If image is empty, try one more time or use noImage.png, set in IB
if (imageData == nil){
NSLog(#"imageData is empty before trying .jpeg");
// If image == nil, try to replace .jpg with .jpeg, and if that worked, set cellImage to that image. If that is also nil, use noImage.png, set in IB.
imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[postsArrayImgSrc objectAtIndex:loop] stringByReplacingOccurrencesOfString:#".jpg" withString:#".jpeg"]]];
}
if (imageData != nil){
NSLog(#"imageData is NOT empty when creating file");
[fileManager createFileAtPath:[rootPath stringByAppendingPathComponent:[NSString stringWithFormat:#"images/%#.png", postID]] contents:imageData attributes:nil];
[writeToPlist addObject:[NSString stringWithFormat:#"%#.png", postID]];
} else {
[writeToNoImagePlist addObject:[NSString stringWithFormat:#"%#", postID]];
}
imageData = nil;
loop++;
NSLog(#"imagePlist: %#\nnoImagePlist: %#", writeToPlist, writeToNoImagePlist);
}
NSMutableArray *writeToAllPlist = [NSMutableArray arrayWithArray:writeToPlist];
[writeToPlist addObjectsFromArray:[NSArray arrayWithContentsOfFile:nowPlist]];
[writeToAllPlist addObjectsFromArray:[NSArray arrayWithContentsOfFile:[rootPath stringByAppendingPathComponent:#"imagesStored.plist"]]];
[writeToNoImagePlist addObjectsFromArray:[NSArray arrayWithContentsOfFile:[rootPath stringByAppendingPathComponent:#"noImage.plist"]]];
[writeToPlist writeToFile:nowPlist atomically:YES];
[writeToAllPlist writeToFile:[rootPath stringByAppendingPathComponent:#"imagesStored.plist"] atomically:YES];
[writeToNoImagePlist writeToFile:[rootPath stringByAppendingPathComponent:#"noImage.plist"] atomically:YES];
[releasePool release];
}
It is as simple as returning a bool at the bottom of the selector being run in the background, and reaload the UITableView.
Thanks to #iWasRobbed:
I have never done this, but just speculating: have you tried returning a BOOL at the very end so that the reloadData function will return TRUE when it gets to that point? I am assuming (possibly incorrectly) that the device serially handles tasks one-at-a-time, so give it a shot.