In the following code:
- (NSMutableArray *) fetchNotesForGroup: (NSString *)groupName {
// Variables declaration
NSMutableArray *result;
NSFetchRequest *fetchRequest;
NSEntityDescription *entity;
NSSortDescriptor *sortDescriptor;
NSPredicate *searchPredicate;
NSError *error = nil;
// Creates the fetchRequest and executes it
fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
entity = [NSEntityDescription entityForName:#"Note" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"noteName" ascending:YES] autorelease];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[fetchRequest setReturnsDistinctResults:YES];
searchPredicate = [NSPredicate predicateWithFormat:#"categoryName like %#", groupName];
[fetchRequest setPredicate:searchPredicate];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"noteName"]];
result = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
// Variables release
return result;
}
... I Fetch notes for a given categoryName. When I'm running Instruments, it says that a NSCFString is leaking.
I know leaks are mean for iPhone developers... but I don't have any idea on how to plug this one.
Any clues? All help is welcome.
Thanks a lot!
Your problem is this:
result = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
// Variables release
return result;
mutableCopy returns an owning reference (ie, an object with a +1 retain count), which you are responsible for (auto)releasing. You don't, and you then relinquish the reference, which means you've leaked the array.
Use return [result autorelease]; instead.
First of all, Instruments may not be always accurate.
It can report leaks on some special case, just because it don't see you are actually releasing the object elsewhere.
It's also possible that some parts of the CF have leaks.
What I can see in your code is that you are using auto-released objects.
You're temporary objects will have a long life-cycle.
What append if you release them explicitly instead, just before the method's return?
You are also returning a copy of an object.
You have to make sure that object is released at some point.
It may be much better to return an auto-released object, and let the calling method decide if the object should be retained.
Since you don't create any strings, the leaking string is most likely one of the strings that ends up inside the fetch request and it is most likely the fetch request that is actually leaking. (The call stack shown in Instruments should confirm this.)
I don't think you have an actual leak but by doing this:
fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
... you are allowing the fetchRequest to live beyond the scope where it was defined Instruments will interpret that as a leak.
Autorelease actually makes objects live longer than direct release because it causes objects to hang around until the outer autorelease pool is drained. For example, if you create object-B inside object-A and return it to object-C autoreleased, object-B will stay alive long after object-A has been deallocated even if object-C never retains it. (Although it will eventually die at an unpredictable moment.)
Autorelease is not a convenience method for retaining. It has a specific purpose of retaining objects that are being passed between other objects. If your not doing this, don't use autorelease.
If you do this:
fetchRequest = [[NSFetchRequest alloc] init];
// ...
[fetchRequest release];
... your leak will go away.
You might, however, want to do this:
return [result autorelease];
... to ensure that the result array lives long enough to be retained by another object.
Related
I have a coredata project that I'm trying to programmatically update a number.
I'm retrieving objects from CoreData and then storing it into an array.
Then, I'm looping through that array to see if the current user's IP is present in the database and trying to update the number of times accessed for that specific array.
The problem is, it's updating all the objects, not just the current object in the looped array.
First, I get the info from core data like so:
- (void)fetchRecords {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"IPAddr" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"ipDate" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setIpArray: mutableFetchResults];
}
Now, I'm trying to find if the current User IP is present in the fetched results, and if it's present, update the number of times accessed:
// see if the ip is present and update if necessary
-(void)ipPresent {
NSString * theCurrentIP = [self getGlobalIPAddress];
for (IPAddr *allips in ipArray)
{
if ([allips.ipNum isEqualToString:theCurrentIP]) {
NSLog(#"The IP %# was found.", theCurrentIP);
// update the ip
NSError *error = nil;
NSNumber *ipToUpdate = allips.ipAccess;
NSNumber *addIpAccess = [[NSNumber alloc] initWithInt:1];
NSNumber *updateIpAddress = [NSNumber numberWithFloat:([ipToUpdate floatValue] + [addIpAccess floatValue])];
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
if ([self.managedObjectContext save:&error]) { // write to database
NSLog(#"The IP Was Updated from %# to %#", ipToUpdate, updateIpAddress);
} else if (![self.managedObjectContext save:&error]) {
NSLog(#"failed with error: %#", error);
}
break;
} else {
NSLog(#"The IP %# was NOT found.", theCurrentIP);
}
}
}
I'm pretty sure the issue is with this line:
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
Again, it's updating ALL the entities and not just the one in the current loop.
Indeed. You are using the wrong method. self.ipArray is a NSMutableArray.
The method
- (void)setValue:(id)value forKey:(NSString *)key
is used for Key-Value Coding (which is what makes it work for Core Data objects), but when applied to an array, it will invoke setValue:forKey: on each entry in the array.
Now, you can see that you could also call setValue:forKey on the one single array element allips since its property is obviously KVC compliant -- otherwise you would be having a different problem, not see the values being set.
Note, that you could also just assign the property...
allips.ipAccess = updateIpAddress;
EDIT
Sorry, probably should have read slower... You do understand that you don't have to use a mutable array, right? You are not actually changing the array, just the elements in the array. An immutable collection means that the collection contents can not change, but when you have a pointer to an object, as long as that object is not immutable, you can still mutate its properties.
Thus, if you had an immutable array of Foo objects, you could do this...
for (Foo *foo in myImmutableArray) {
Bar *bar = [self getSomeNewBar];
[foo setBar:bar];
// If Foo is KVC compliant, you can do this too...
[foo setValue:bar for Key:#"bar"];
}
If, however, you call setValue:forKey on the array, it will be invoked for each element of the array. Note, that setValue:forKey is actually declared in the immutable NSArray.
EDIT
That comment was hard to read.
The core data object is just another object. It looks like you have subclassed it, and provided it with properties for the attributes. Just replace
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
with
[allips setValue:updateIpAddress forKey:#"ipAccess"];
or
allips.ipAccess = updateIpAddress;
Either of those should modify your core data object, as they would any object that had a read/write property named "ipAccess"
Assuming, of course, that I didn't read it wrong again... and allips is your core data object...
My guess is I'm missing some Core Data fundamental understanding here, but here goes:
I have several fetch requests in my app to handle retrieval of different things. In some cases the code runs fine, returning the requested objects.
In some cases, it returns what appear to be already-released objects (e.g. just a few lines of code later trying to reference the returned result gives EXC_BAD_ACCESS). As I set various breakpoints and log statements in the code and step through it will also get the occasional SIGABRT or EXC_BAD_ACCESS in other locations in the code.
In every case, it appears to be when I go to reference the result of a fetch request.
Here's a sample of one such fetch request:
// Who am I?
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *signedInPersonId = [defaults stringForKey:#"signedInPersonId"];
// Return (if any) the Request object with given UUID
RequestStrings *r = [[RequestStrings alloc] init];
NSEntityDescription *description = [NSEntityDescription entityForName:r.table_Request inManagedObjectContext:moc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(UUID == %#) && (dataOwnerId == %#)", UUID, signedInPersonId];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:description];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *requests = [moc executeFetchRequest:request error:&error];
Request *returnRequest = nil;
if (requests != nil) {
if ([requests count] > 0) {
NSLog(#"getRequestWithId - requests array: %#, first: %#", requests, [requests objectAtIndex:0]);
returnRequest = [requests objectAtIndex:0];
}
else {
returnRequest = nil;
}
}
[r release];
[request release];
return returnRequest;
P.S. Here's some more info
In some instances the same code will return the desired objects, or throw an exception stating that [NSCFNumber length] is an unrecognized selector. Not sure how the same entity description + fetch request could return an array in one case and a Number in the other.
You're basically doing this :
// (1) Create an array of stuff
NSArray *myArray = [NSarray arrayWithObjects:a, b, c, nil];
// (2) Take the first one off
id myObject = [myArray objectAtIndex:0];
// (3) Release everything
[myArray release];
// (4) Return myObject
return myObject;
You're just using CoreData to do step (1).
Step (1) returns an array of objects. The only thing retaining these objects is the array that they're in*. If you release the array (step 3), you will release all the objects that are inside it. Why do you expect myObject to still exist by step (4)?
Try this :
// Make sure that we keep a retain of returnRequest
returnRequest = [[[requests objectAtIndex:0] retain] autorelease];
*For the pedantic out there : I've made an assumption that makes my answer simpler. In the real world you don't know what's retaining your objects - that's up to the framework as well as your code. However, it's good practice to retain anything you expect to keep around.
Why don't you do all the releases at the end before the return? This should fix your issue as you're releasing things immediately after creating them.
#deanWombourne - good suggestions, and I think you're right on. It turns out I actually was crashing from a zombie (wasn't using my own setters in a previous view controller when I was using values from a previously fetched managed object).
The solution: EXC_BAD_ACCESS is always worth a trip down zombie lane. Do your due diligence on memory management.
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 am getting a weird crash when I try to save my model. This is my code:
TJModel *model = [TJModel sharedTJModel];
NSFetchRequest *request = [[[NSFetchRequest alloc] init]autorelease];
[request setReturnsObjectsAsFaults:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TJVideoList"inManagedObjectContext:[model managedObjectContext]];
[request setEntity:entity];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[[model managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
if (error != nil)
NSLog(#"error %#",[error localizedDescription]);
TJVideoList *videoList = nil;
if ([mutableFetchResults count] == 0) {
videoList = (VideoList *)[NSEntityDescription insertNewObjectForEntityForName:#"TJVideoList"
inManagedObjectContext:[model managedObjectContext]];
}
else
{
videoList = [mutableFetchResults objectAtIndex:0];
}
[videoList addVideoListObject:recordedVideo];
error = nil;
if (![[model managedObjectContext] save:&error]) {
And crash .....This is what said in the terminal:
-[NSConcreteValue UTF8String]: unrecognized selector sent to instance 0x1d33f0
I thought it might be a cuestion of deallocated objects, so I retained them like this:
[managedObjectContext setRetainsRegisteredObjects:YES];
With no luck.
Your crash is not a result of this code.
Crashes in saves usually result from an error with an managedObject's attributes. In this case, you have somewhere assigned the wrong value to a string attribute. When the context goes to convert the string attribute to a UTF8 string for persistence, the object that is there instead of the NSString does not understand the message and the crash results.
Although this code should run okay, you do have some risky practices:
NSFetchRequest *request = [[[NSFetchRequest alloc] init]autorelease];
This is a bad practice. autorelease is the same as release. You should not send it to an object until you are complete done with it. autorelease marks an object for death the next time the memory pool is drained. In some case, that will kill the object unexpectedly. While it won't cause problems here, you don't want to get into the habit of taking this short cut because it will eventually bite you.
You should only use autorelease when the current scope is done with the object but the object is being sent outside the scope (usually in a method return.)
NSMutableArray *mutableFetchResults = [[[model managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
The mutable array here is pointless as is the copy. This is apparently in some reference material somewhere because it keeps cropping up novices code in the last few months. If you're not going to alter an array there is no reason to have it mutable. In the case of an array of managed objects, it is pointless to copy the array.
videoList = [mutableFetchResults objectAtIndex:0]
Since you have no sort descriptor for the fetch, the mutableFetchResults array will be in a random order. If you have more than one object returned, which is almost always the case, you will get a random TJVideoList object at the zero element every time you run the code.
Sounds more like you assigned an NSValue instance (NSNumber, most likely, as it is the most commonly used subclass) where an NSString was expected. The -retainsRegisteredObjects: is unlikely to be needed (it won't fix a memory related problem anyway).
It might possibly be an over-release issue, too. Try running with Zombie detection enabled (see the Run menu).
Code:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"A"
inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"id" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"somePredicate", someObject];
[fetchRequest setPredicate:predicate];
frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"recency"
cacheName:#"frc"];
[fetchRequest release];
frc.delegate = self;
NSError *error;
BOOL success = [frc performFetch:&error];
if (!success) {
NSLog(#"error: %#", error);
}
for (A *a in [frc fetchedObjects]) {
[someMutableArray addObject:a.b];
[someMutableArray addObject:a];
}
Data model:
A and B are entities. A has mandatory to-one relation to B. B has inverse optional to-many relation to A.
Above in English:
Initialize a NSFetchedResultsController to grab some data to power a tableview. After initial fetch, set the data aside for some processing.
Now, later, I try to do this:
id object = [someMutableArray objectAtIndex:someIndex];
NSLog(#"%#", object);
if ([object isMemberOfClass:[B class]]) {
someVar = object.propertyFromB; // problem
} else if ([object isMemberOfClass:[A class]]) {
someVar = object.propertyFromA;
}
Question/problem: the line indicated with "problem" crashes. (EDIT: See below for resolution, but would still like an explanation.)
The NSLog call above yields:
2010-01-30 14:47:14.433 app[22618:20b] <B: 0xf7f750> (entity: B; id: 0xf7ba70 <x-coredata://B01FEC86-14D6-4973-BFDB-EDE4AFD24FDC/B/p4> ; data: <fault>)
2010-01-30 14:47:14.438 app[22618:20b] <A: 0xf7e360> (entity: A; id: 0xf35820 <x-coredata://B01FEC86-14D6-4973-BFDB-EDE4AFD24FDC/A/p6> ; data: {
prop1 = value1;
prop2 = value2;
... etc ...
})
I.e by the problematic line, if the object was of type A, it has been faulted and is available in memory, but if it is B, it's a fault.
My understanding is that the "problem" line should fire the fault and fetch the data from store, but this is not happening. I would like to understand/debug why. I have tried inserting willAccessKey/didAccessKey calls around this. I also tried to set setRelationshipKeyPathsForPrefetching:"b" on the fetch request. Neither worked.
My hypothesis is that since I'm somewhat abusing the NSFetchedRequestController results, the faulting engine gets confused along the way and doesn't fetch the fault when it's supposed to. So I guess a bruteforce way would be to create a new manual fetch request to fetch the related B object at the right time. But is there a better way?
EDIT:
The problem was that object B had a property "description" that I had defined, but that collides with NSObject's built-in name. Xcode always gave me warnings, but I ignored them because I thought "description" internal property/method is only used for dumping strings to console and the like, not internal processing.
The problem disappeared after I made a new version of my model, renaming "description" to something else. All the faulting started to work as expected.
I don't understand, though, what is going on. Is Core Data using the objects' "description" method for some internal introspection?
From Core Data Programming Guide
You are discouraged from overriding description—if this method fires a fault during a debugging operation, the results may be unpredictable—and initWithEntity:insertIntoManagedObjectContext:. You should typically not override the key-value coding methods such as valueForKey: and setValue:forKeyPath:.
-description is a method in NSObject that returns a string representation of your object. In the line NSLog(#"%#", object), -description is used to get the string that you see in the console. Key-value coding will end up using the method to get the property for the description attribute. This causes a whole lot of confusion to Core Data.
The programming guide is being generous when it says "discouraged". They really mean "Yeah, it's going to break your stuff."
That link also has a good list of other methods that will break your stuff if you override them.
you need to treat description as a reserved word. That is the issue you are having. You should have received a warning when you tried to have a property called description.
For custom descriptions you are welcome to override -(NSString *)debugDescription from NSObject Protocol.
From Apple's doc:
NSObject implements this method by calling through to the description method.
Thus, by default, an object’s debug description is the same as its
description. However, you can override debugDescription if you want to
decouple these.