Instruments shows the following code leaks, if I comment out this code there is no leak.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:USER_CORE_DATA inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicte = [NSPredicate predicateWithFormat:#"username == %#", [[User defaultManager] savedUsername]];
[fetchRequest setPredicate:predicte];
// set any predicates or sort descriptors, etc.
// execute the request
[self.managedObjectContext executeFetchRequest:fetchRequest onSuccess:^(NSArray *results) {
} onFailure:^(NSError *error) {
NSLog(#"Error fetching: %#", error);
}];
[fetchRequest release];
Specifically instruments says this line in the code above:
[self.managedObjectContext executeFetchRequest:fetchRequest onSuccess:^(NSArray *results)
It appears to be a leak with fetchRequest and/or the block. Any help will be appreciated, and thanks in advance.
It appears executeFetchRequest:onSuccess:onFailure: is a function you have defined in NSManagedObjectContext category. Ensure the NSArray object instance that you are passing to the onSuccess block is autoreleased.
Actually it turned out that StackMob had a leak in their code, I downloaded there source and fixed it.
- (NSString *)primaryKeyField
{
NSString *objectIdField = nil;
// Search for schemanameId
objectIdField = [[self SMSchema] stringByAppendingFormat:#"Id"];
if ([[[self entity] propertiesByName] objectForKey:objectIdField] != nil) {
return objectIdField;
}
objectIdField = nil; // This line was missing and causing a leak
// Search for schemaname_id
objectIdField = [[self SMSchema] stringByAppendingFormat:#"_id"];
if ([[[self entity] propertiesByName] objectForKey:objectIdField] != nil) {
return objectIdField;
}
objectIdField = nil; // This line was missing and causing a leak
// Raise an exception and return nil
[NSException raise:SMExceptionIncompatibleObject format:#"No Attribute found for `entity %# which maps to the primary key on StackMob. The Attribute name should match one of the following formats: lowercasedEntityNameId or lowercasedEntityName_id. If the managed object subclass for %# inherits from SMUserManagedObject, meaning it is intended to define user objects, you may return either of the above formats or whatever lowercase string with optional underscores matches the primary key field on StackMob.", [[self entity] name], [[self entity] name]];`
Related
Hi i am saving values coming from a database in a MultableArray and then in CoreData:
NSMultableArray *mutary = [[NSMultableArray alloc] init];
NSManagedObjectContext *context = [app managedObjectContext];
for(int n=0; n<[AttributeArray count]; n++)
{
[mutary addObject:[[AttributeArray objectAtIndex:n] objectForKey:#"AttributName"]];
NSLog(#"%#", mutary);
}
attributeString = [mutary componentsJoinedByString:#","];
raume = [NSEntityDescription insertNewObjectForEntityForName:#"Raum" inManagedObjectContext:context];
raume.raumattribut = attributeString;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog output for the MultableArray is:
2012-06-20 17:21:00.047 book-app[31984:15803] (
A7OVERHEAD,
Beamer
)
So far its working correct. The two expected values from the database are now in the Array.
Now i am fetching these attributes from CoreData:
NSManagedObjectContext *context = [app managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Raum" inManagedObjectContext:context];
[request setEntity:entity];
NSError *error = nil;
NSArray *events = [context executeFetchRequest:request error:&error];
for (Raum *att in events)
{
stringAttribute = [[events valueForKey:#"raumattribut"] componentsJoinedByString:#","];
NSLog(#"ATTRIBUTE: %#", stringAttribute);
}
}
So far so good. But if i look now on my NSLog output:
2012-06-20 17:21:00.055 book-app[31984:15803] ATTRIBUTE: <null>,A7OVERHEAD,Beamer
CoreData is returning and then the two values. Where is that coming from?
Can someone help?
Thanks in advance
EDIT: After some investigating and clarifying (see comments), here is my answer:
Calling valueForKey on an NSArray returns an array where each element is created by calling valueForKey on each member of the original array. This means your output indicates that you have 3 total objects returned by your fetch request, and the first one does not have it's raumattribut attribute set.
PREVIOUS ANSWER:
You are calling valueForKey on an instance of NSArray (the query results). Perhaps you need to get objectAtIndex:0 first use the variable you are iterating the values on? valueForKey is likely returning nil.
stringAttribute = [[att valueForKey:#"raumattribut"] componentsJoinedByString:#","];
or similar.
EDIT:
At first I missed the for loop in the second part, I have edited the above code sample to use the attr, which
I'm implementing a car manager for an iPhone app and I have trouble saving a new car. So I have a "Car" entity (and per-loaded DB), containing multiples attributes. I have 2 booleans "saved" and "selected" to track which car the user added to his list (if added, saved = 1) and which car is currently selected. So when I create a new car, I deselect the old one (selected=0), and want to modify the new car to set its attributes saved=1 and selected=1.
Here is my functions:
- (IBAction)save
{
// Disable the previous car selection.
[self deselectCurrentSelectedCar];
// Add new car as saved and selected.
[self saveAndSelectNewCar];
// Call the delegate to dismiss the modal view.
[_delegate dismissAndSave];
}
- (void)deselectCurrentSelectedCar
{
// Fetched saved car.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selected = 1"];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: car not found.");
}
else {
// Get car and assign new selected value.
Car *carToSave = (Car *)[mutableFetchResults objectAtIndex:0];
[carToSave setSelected:[NSNumber numberWithInt:0]];
// Save the car.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: error saving car.");
}
else {
NSLog(#"[AddCarViewController] deselect car: car saved.");
}
}
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
- (void)saveAndSelectNewCar
{
// Get the car, and pass to the delegate the new settings.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(year=%#) AND (make=%#) AND (model=%#) AND (d=%#) AND (n=%#)", _car.year, _car.make, _car.model, _car.d, _car.n];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle error.
NSLog(#"[AddCarViewController] save and select: can't save the new car");
}
else {
// Get the car selected.
Car *selectedCar = (Car *)[mutableFetchResults objectAtIndex:0];
[selectedCar setValue:[NSNumber numberWithInt:1] forKey:#"selected"];
[selectedCar setValue:[NSNumber numberWithInt:1] forKey:#"saved"];
// Save the car.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"[AddCarViewController] save and select: error saving car.");
}
else {
NSLog(#"[AddCarViewController] save and select: car saved.");
}
// Add car to delegate.
[EcoAppAppDelegate setUserCar:selectedCar];
}
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
And I have this log all the time: "error saving car." on both functions. So there is definitely something wrong.
Also, it's pretty anoying to fetch the car I want to modify it, instead of doing right away an update. If there another please tell me!
Thanks.
In your header file, you should set up a mutable array for your cars.
NSMutableArray *carArray;
and
#property (nonatomic, retain) NSMutableArray *carArray;
Then make sure to synthesize it in your implementation file. Then when you fetch from your managed object context, you can set your array with the contents returned
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: car not found.");
} else {
[self setCarArray:mutableFetchResults];
}
Doing this will hold onto all the objects in your managed object context, so when you want to modify a Car, you can find it there instead of fetching again. If you need to sort it, you can apply a sort descriptor initialized with one of your attributes as a key. For example:
NSSortDescriptor *sorter = [NSSortDescriptor sortDescriptorWithKey:#"selected" ascending:YES];
[carArray sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
This will sort the Cars based on whether they are selected or not.
As for why you're getting an error, that could be one of many reasons. Typically the MOC will fail to save if one of your attributes is set to nil, so that might be the cause. You can get some detail from it if you set something like the following up
if (![self.managedObjectContext save:&error]) {
NSLog(#"failed with error %#", error);
}
This will return the actual error you ran into. It would also be a good idea to set up a log to make sure you have Car specified. Something like NSLog(#"selected car %#", carToSave); just to be safe
I want to write generic method that checks if a given entity is in a Core Data database. I would like to have one method that works for all entities. I came up with something like this:
-(BOOL)checkIfExistsEntity:(NSString *)entityName withFieldName:(NSString *)fieldName andFieldValue:(NSString *)value{
NSManagedObjectContext *managedObjectContext = [(FGuideAppDelegate *) [[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *selectEntityDescription = [NSEntityDescription
entityForName:entityName inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:selectEntityDescription];
NSPredicate *whereForFetch = [NSPredicate predicateWithFormat:#"%# = %#",fieldName, value];
[fetchRequest setPredicate:whereForFetch];
NSError *error = nil;
NSArray *array = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (array != nil && [array count] > 0){
return YES;
}else {
return NO;
}
}
However it looks like the string #"%# = %#" in the predicate I wrote is not parsed properly. Is there any way to implement described functionality without hardcoding entities properties in a predicate?
Check out dynamic property names in the link below.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Predicates/Articles/pCreating.html
Instead of %#, using %K should solve your problem
you need == not = (I think....)
You could use the countForFetchRequest method.
Ok, I thought I had this but I am not getting the results that I am expecting. Hopefully someone can help.
I have two entities Person and Timesheet with one attribute to-many relationship:
Person.timesheet<--->>Timesheet.user.
The code below works but when I try to add a second timesheet entry it seems to override the first?
I have looked at the Apple Docs and they are a little vague on this subject.
//Add
NSManagedObjectContext *context = self.managedObjectContext;
Person *personAdded = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
Timesheet *timesheet = [NSEntityDescription insertNewObjectForEntityForName:#"Timesheet" inManagedObjectContext:context];;
timesheet.time = #"10:00 Friday";
timesheet.timestamp = [NSDate date];
NSSet *timesheetSet = [NSSet setWithObject:timesheet];
personAdded.name = #"Darren";
personAdded.job = #"Job to be Done";
personAdded.timesheet = timesheetSet;
NSError *error = nil;
[context save:&error];
if (error) {
NSLog(#"[ERROR] COREDATA: Save raised an error - '%#'", [error description]);
}
NSLog(#"[SUCCESS] COREDATA: Inserted new User to database!");
// Load
NSEntityDescription *personEntity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: personEntity];
error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (!results || error) {
NSLog(#"[ERROR] COREDATA: Fetch request raised an error - '%#'", [error description]);
[request release];
}
NSLog(#"Results: %#",results);
Person *firstUser = [results objectAtIndex:0];
NSLog(#"First User's name: %#",firstUser.name);
NSLog(#"First User's time %#",[[firstUser.timesheet anyObject] valueForKeyPath:#"timestamp"]);
I am wondering if it could be because I am actually setting the Person.timesheet key with the NSSet and not the actual Table? OR could it be that I am not calling the results correctly?
Thanks,
Darren
You should have a generated "CoreDataGeneratedAccessors" method in your Person.h file which gives you a method
[personAdded addTimesheet:timesheetSet];
which creates the links for you ?
Core Data normally generates an add and a remove method for any relationships you define.
Post your person.h file if this is not clear.
I am currently trying to populate a UITableView in my project from Core Data using NSFetchedResultsController. I am using a custom search with a comparator (although I have also tried a selector and had an identical problem):
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"objectName" ascending:YES comparator:^(id s1, id s2) {
NSLog(#"Comparator");
//custom compare here with print statement
}];
NSLog(#"Sort Descriptor Set");
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"firstLetterOfObject" cacheName:#"Objects"];
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
if (![fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchedResultsController;
When I enter this tab, I have logged all over the program and found that the NSFetchedResultsController does not even enter the comparator block when fetching. It instead sorts it with some default sorting method.
If I delete and add an Object with an objectName, however, it then does enter the comparator block and correctly sort the table.
Why does the NSFetchedResultsController not sort using the comparator until the managed object model is changed?
Notes: I have tried also turning off caching, and/or performing a fetch in viewDidLoad:, but it seems that how many times I fetch does not matter, but when. For some reason it only uses my sorting after the object model has been changed.
There are a couple of things I can think of. First, though this may not be your problem, you cannot sort on transient properties. But more likely is that when sorting in a model backed by a SQL store, the comparator gets "compiled" to a SQL query, and not all Objective-C functions are available. In this case, you'd need to sort in memory after the fetch is performed.
EDIT: See this doc, specifically the Fetch Predicates and Sort Descriptors section.
I see the same problem and a way to work around it is to modify an object, save the change then restore it to its original value and save again.
// try to force an update for correct initial sorting bug
NSInteger count = [self.fetchedResultsController.sections count];
if (count > 0) {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:0];
count = [sectionInfo numberOfObjects];
if (count > 0) {
NSManagedObject *obj = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
NSString *name = [obj valueForKey:#"name"];
[obj setValue:#"X" forKey:#"name"];
// Save the context.
[self saveContext];
[obj setValue:name forKey:#"name"];
// Save the context.
[self saveContext];
}
}
Sorry, but did you miss the final fetch part to your code snippet?:
NSError *error;
BOOL success = [aFetchedResultsController performFetch:&error];
Don't forget to release the request too:
[fetchRequest release];