I desperately need help with a memory leak in my iPhone app. The app is ready to submit to the app store, is stable, has no memory leaks at all in iPhone simulator or Clang ... but seems riddled with them on my iPod Touch.
They all seem to stem from managedObjectModel when I'm trying to retrieve data from Core Data.
The Core Data code in my app was automatically created by Xcode a while back, I've noticed that the code has since changed when you get xcode to generate it ... I've tried with the old and new but it makes no difference.
If I comment out the following code, the problem goes away ... can anyway see what's wrong with it? I've spent 9 hours on this so far and just can't figure it out!
NSString *entityForName = [[NSString alloc] initWithString:#"OfflineSettings"];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityForName inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
[entityForName release];
NSSortDescriptor *sortById = [[NSSortDescriptor alloc] initWithKey:#"Id" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortById]];
[sortById release];
NSError *error;
NSMutableArray *mutableFetchResults = [[[self managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"Error fetching");
}
int intId = -1;
if ([mutableFetchResults count] == 0) {
TTDERROR(#"No id has been saved to offline settings");
} else {
OfflineSettings *offlineSettings = (OfflineSettings *)[mutableFetchResults objectAtIndex:0];
intId = [offlineSettings.Id intValue];
}
[mutableFetchResults release];
[request release];
The leak specifically seems to be on this line:
NSMutableArray *mutableFetchResults = [[[self managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
.. and the code for [self managedObjectContext] is as follows in case it helps ..
- (NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext_ != nil) {
return managedObjectContext_;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext_;
}
I'm really at a loss, so I would be so grateful for some help!
Steven
You don't need the mutable copy. executeFetchRequest: returns an autoreleased static array and you're not mutating the array. (I keep seeing this. Must be in an example somewhere.) Likewise, creating the entityForName NSString is pointless. Just put the string literal in the entityForName: to eliminate another possible source of error.
Niether of these are the likely source of the leak but you should remove them anyway.
As a rule of thumb, if you have troubles on device but not simulator or on one hardware but not others, then the problem is in a library/framework that is not properly compiled for the hardware where the error occurs. There really isn't any type of coder error that leaks in one environment but not others. When we make a mistake, it's universal.
It's also possible for resources such as images and sounds to behave differently because different devices use different graphics and audio hardware. That, however, is rather rare.
If you run the code through Instruments it should tell you exactly what object is leaking.
Related
I want to deal with a data attribute, named originalImage in every Media entity in the store.
The problem is that despite the autoreleasing, memory builds up every time it's accessed via valueForKey, and eventually the app crashes. Or perhaps it's loading large individual NSData items which is the problem, but Instruments shows it to be a steadily inclining graph of memory usage, until it eventually gives me a memory warning and then crashes.
I haven't started on the rest of the code for this yet, so i'm not performing some hidden task that i'm not showing you.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Media"
inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
[request setFetchBatchSize:10];
NSArray *mediaItems = [[self managedObjectContext] executeFetchRequest:request error:nil];
for (NSManagedObject *media in mediaItems) {
#autoreleasepool {
[media valueForKey:#"originalImage"];
}
}
EDIT: Today it seems that even just mentioning the NSManagedObject media is enough to cause this media leak. So even without the valueForKey line, i have a leak. I've tried this:
while (i < count) {
#autoreleasepool {
NSManagedObject *media = [mediaItems objectAtIndex:i];
[[self managedObjectContext] refreshObject:media mergeChanges:NO];
NSLog(#"i: %i", i);
i++;
}
}
This also didn't work, and crashed at the same point.
Did you try something like:
for(int i=0;i<[mediaItems count]; i++) {
#autoreleasepool {
NSManagedObject *media = [mediaItems objectAtIndex:i];
[media valueForKey:#"originalImage"];
}
}
Maybe this way "media" gets released properly at every loop.
(Sorry, I put this on a comment. I'm new on SO and not enough rep.)
I am getting a memory leak on the following code line:
The code-line:
NSArray *fetchedObjects = [qContext executeFetchRequest:fetchRequest error:&error];
I have been trying to read-up on this and have tried to find the cause of this for quite some time, without success.
Could someone give me a hint where to look? ...and do i understand the "100%" correctly that it indicate that this code line is 100% causing the leak?
UPDATE
Some more code:
//=========PREPARE CORE DATA DB===========//
if (managedObjectContext == nil) { managedObjectContext = [(FamQuiz_R0_1AppDelegate *)
[[UIApplication sharedApplication] delegate] managedObjectContext]; }
// Define qContext
NSManagedObjectContext *qContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"questions" inManagedObjectContext:qContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [qContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
if ([[info valueForKey:#"qDiff"] intValue] == 1) {
[allEasyArrayQ addObject:[info valueForKey:#"idQ"]];
} else if ([[info valueForKey:#"qDiff"] intValue] == 2) {
[allMediumArrayQ addObject:[info valueForKey:#"idQ"]];
} else if ([[info valueForKey:#"qDiff"] intValue] == 3) {
[allHardArrayQ addObject:[info valueForKey:#"idQ"]];
}
}
You are probably over-retaining the array or the objects inside it later in this method or even outside of this method. The line will only indicate where the objects are created, not where the actual extra retain is performed. For that you'd need to check using Instruments which call stacks retain and release the leaked objects.
No, the 100% means that this leak accounts for 100% of your leaked memory (ie, this is you only leak).
Note if you want to fix the leak you are looking in the wrong place. Leaks tells you which instance is leaking, it's Class and it's memory management history. Try a tutorial like this one http://www.cimgf.com/2008/04/02/cocoa-tutorial-fixing-memory-leaks-with-instruments/
Note, what Leaks can't tell you is the line where you are leaking, as the cause of your leak is something you haven't done, as opposed to something you have done (ie you didn't release your object).
I have two UITableViewControllers. I'm pushing to the second one and calling the following method in viewDidLoad.
The second time I dispose of this view and go back to the first view, I get a memory leak.
Instruments says the problem's on the last line of the following method.
- (void)fetchRecords {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Articulation" inManagedObjectContext:[self managedObjectContext]]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"articulationGroup == %#", selectedArticulationGroup];
[request setPredicate:predicate];
static NSArray *sortDescriptors = nil;
if (!sortDescriptors)
sortDescriptors = [[NSArray alloc] initWithObject:[[[NSSortDescriptor alloc] initWithKey:#"text" ascending:NO] autorelease]];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
if (!fetchResults)
NSLog(#"no fetch results ArticulationsViewController, error %#", error);
[request release];
self.articulationsArray = [NSMutableArray arrayWithArray:fetchResults];
}
I've got no idea... going to bed :'(
For one thing, you're leaking your sortDescriptors array if you take the allocation branch.
<soapbox> I strongly recommend you use curly braces around all if/else blocks even if they only have one line - these bugs are very difficult to find after the fact</soapbox>
Please post your dealloc method and the ivar declarations.
Well, I notice two things which might be correct but I wanted to ask about anyway.
First of all, ur asking in the if statement: if (!fetchResults). That would mean fetchResults possibly doesn't exist. Still, you try to initialize an array with that.
Secondly, I don't know how you allocated articulationsArray, but does changing the line into self.articulationsArray = [[NSMutableArray alloc] initWithArray:fetchResults]] have any effect?
Why is your sortDescriptors array static? Typically, you would do something like this:
NSSortDescriptor *textSort = [[NSSortDescriptor alloc] initWithKey:#"text" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:textSort]];
[textSort release];
Also, after if (!fetchResults) you shouldn't save your array, but do something like this:
if (!fetchResults)
{
NSLog(#"no fetch results ArticulationsViewController, error %#", error);
}
else
{
NSMutableArray *articulationsArray_tmp = [fetchResults mutableCopy];
self.articulationsArray = articulationsArray_tmp;
[articulationsArray_tmp release];
}
[request release];
Also note how you could set the articulationsArray differently. One should always be careful with these NSMutableArrays ... :)
This block is completely unnecessary and it is dangerous:
static NSArray *sortDescriptors = nil;
if (!sortDescriptors)
sortDescriptors = [[NSArray alloc] initWithObject:[[[NSSortDescriptor alloc] initWithKey:#"text" ascending:NO] autorelease]];
[request setSortDescriptors:sortDescriptors];
Any static object is dangerous to memory and you use them only in special cases. Why nail an array with only a local scope to a specific address/block? All this can be replaced with one line:
[request setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortWithKey:#"text" ascending:NO]]];
... and its done.
This line is probably unnecessary and likely to cause problems later:
self.articulationsArray = [NSMutableArray arrayWithArray:fetchResults];
Why would you need a mutable array of fetched objects? You can't really add or remove anything from the array directly while maintaining your graph integrity without refetching.
Just:
self.articulationsArray = fetchResults;
will work fine for most cases.
The more objects you create, the more chances for a leak you create. Keep things as simple as possible.
Well... I feel like a goose.
When I popped back to my first tableViewController, I wasn't releasing articulationsArray.
I was using
- (void)viewDidUnload {
[self.articulationsArray release];
}
When I should have been using:
-(void)viewDidDisappear:(BOOL)animated {
[self.articulationsArray release];
}
ViewDidUnload, was never being called.
Thanks for your help everyone.
I have an app where I import a bunch of data and I want to to happen on a background thread. This was working well for a small amount of data but now it seems I am running into this error midway through the parsing and importing of large amounts of my data into Core Data:
Program received signal: “EXC_BAD_ACCESS”.
Here is the call to the background thread:
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
Here is an example of what I am doing in my code:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// load manufactuers from DB
NSFetchRequest *crequest = [[NSFetchRequest alloc] init];
NSEntityDescription *manufacturer = [NSEntityDescription entityForName:#"Manufacturer" inManagedObjectContext:managedObjectContext];
[crequest setEntity:manufacturer];
NSError *cerror=nil;;
NSArray *manufacturers = [[managedObjectContext executeFetchRequest:crequest error:&cerror]mutableCopy];
[crequest release];
for (int m=0; m < [manufacturers count]; m++) {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:kClientListURL, [[manufacturers objectAtIndex:m]ManufacturerID]]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setNumberOfTimesToRetryOnTimeout:2];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *responseString = [request responseString];
NSArray *items = [responseString JSONValue];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSNumberFormatter *dec = [[NSNumberFormatter alloc]init];
[dec setNumberStyle:NSNumberFormatterDecimalStyle];
for (int i = 0; i < [items count]; i++)
{
Client *entity = (Client*) [NSEntityDescription insertNewObjectForEntityForName:#"Client" inManagedObjectContext:managedObjectContext];
[entity setCompanyName:[[items objectAtIndex:i] objectForKey:#"CompanyName"]];
// set a bunch of other properties
[entity setManufacturer:[manufacturers objectAtIndex:m]];
statusMessage = [NSString stringWithFormat:#"importing client: %#", entity.CompanyName];
[self performSelectorOnMainThread:#selector(setStatus) withObject:nil waitUntilDone:YES];
}
[f release];
[dec release];
} else {
NSLog(#"%#",[NSString stringWithFormat:#"JSON parsing failed: %#", [error localizedDescription]]);
}
NSError *entityerror;
if (![managedObjectContext save:&entityerror]) {
// //Handle the error.
NSLog(#"\n\n\n Error saving clients: %# \n\n\n\n",entityerror);
}
}
//More data importing code
[pool release];
As I said this was working fine for a small amount of data but now in he simulator it throws bad access errors at random points.
Then I read this article which confused me:
http://www.cocoadev.com/index.pl?DebuggingAutorelease
Do I not need to release my objects if they are inside of the NSAutoReleasePool? Am I causing them to be released twice at some point?
If I remove the release statements I get potential leaks errors when building using Command-Shift-A?
The rules are:
If you are the owner, you need to either call [object release] or [object autorelease]. The later will delegate the release call to the NSAutoReleasePool, when the pool gets flushed or released.
You are an owner if you called [object retain] or if you got the object through a method that has either alloc or copy in its name.
If you got the object from a method that has neither alloc nor copy in its name and you haven't retained it you must not call release or autorelease on it (except in the very rare case that the documentations states that you are the owner).
So yes, if you got an autoreleased object and you call release on it you are over-releasing it. The effects are various but almost always lead to a crash, normally with EXC_BAD_ACCESS. You can debug this by setting the NSZombieEnabled environment variable. That way, whenever an object is deallocated it is replaced by a dummy that knows what object lived at that address before and can then tell you which object got over-released.
But reading through your code I didn't spot any immediately obvious problem, but I don't know Core Data (which you seem to be using) well enough. Another possibility is a threading/timing issue. Also, you said that's not the whole method ? Maybe the error is in the missing part.
I am assuming that importAllData is the method you are using. If so, you are missing the : at the end in your call.
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
should be
[self performSelectorInBackground:#selector(importAllData:) withObject:nil];
Hope that helps!
I've got a REALLY strange problem - my testers report problems (app is hanging, not crashing) on 3GS, but NOT on 3G... this is the code where I'm assuming the problem:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
[FlurryAPI startSession:#"myflurryapisessionkey"];
[FlurryAPI setSessionReportsOnCloseEnabled:NO];
// set managedObjectContext on TabBarController
tabBarController.managedObjectContext = self.managedObjectContext;
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
// check if we have to show a favorite immediately
Favorite *startseiteFavorite = [self getStartSeiteFavorite];
if (startseiteFavorite != nil) {
[FlurryAPI logEvent:#"favorite found"];
[self showStartseiteFavorite:startseiteFavorite];
} else {
[FlurryAPI logEvent:#"no favorite found"];
}
}
- (Favorite *) getStartSeiteFavorite {
// loading values
smart_infoAppDelegate *appDelegate = (smart_infoAppDelegate *)[[UIApplication sharedApplication] delegate];
managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Favorite" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSPredicate * predicate;
predicate = [NSPredicate predicateWithFormat:#"startseite == 1"];
[request setPredicate:predicate];
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[request release];
Favorite *tempFavorite = [mutableFetchResults count] > 0 ? [mutableFetchResults objectAtIndex:0] : nil;
[mutableFetchResults release];
return tempFavorite;
}
I'd like to select a tab according to select the tab according to the existence of such a 'startseiteFavorite' - if there's one, I select tab 1, otherwise tab 0... does anyone of you find a problem or a reason why this works without problems in 3G but not in 3GS?
Thanks a lot,
Stefan
It's pretty tough to figure out a problem like this if you have not actually duplicated the problem yourself? Are you not able to duplicate the problem on the 3GS? You're saying that you are assuming where the problem occurs. That's not the best approach.
Do you not have a 3GS to test on? If not, I would go buy one to test it and get to the bottom of the issue directly in the debugger. You should never ship an app without having the hardware your app says it supports (not sure if you're doing that, but I'm thinking it's possible from the sound of your question).
Ask your testers exactly how they are causing the hang to occur.