iOS - How can I sort the managed objects fetched from CoreData? - iphone

This is how i am inserting the data,
NSEntityDescription * entityDescription = [NSEntityDescription entityForName:[DOSnow entityDescription] inManagedObjectContext: proxy.managedObjectContext];
DOCurrentCondition *doSnow = [[[DOSnow alloc] initWithEntity:entityDescription insertIntoManagedObjectContext: proxy.managedObjectContext] autorelease];
NSXMLElement *snowConditionsElement = [[roseElement elementsForName:SNOW] lastObject];
NSArray *snowElements = [snowConditionsElement children];
for (NSXMLElement *snowElement in snowElements)
{
NSEntityDescription * entityDescription = [NSEntityDescription entityForName:[DOPair entityDescription] inManagedObjectContext: proxy.managedObjectContext];
DOPair *pair = [[[DOPair alloc] initWithEntity:entityDescription insertIntoManagedObjectContext: proxy.managedObjectContext] autorelease];
pair.key = [snowElement name];
pair.value = [snowElement stringValue];
[doSnow addConditionsObject: pair];
}
[proxy save];
And this is how i am fetching the data,
- (NSArray *) fetchSnowConditions
{
ApplicationFacade *appFacade = [ApplicationFacade appFacade];
NSManagedObjectContext *context = appFacade.rProxy.managedObjectContext;
NSFetchRequest * request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:[DOSnow entityDescription] inManagedObjectContext:context]];
NSError *error;
NSArray *result = [context executeFetchRequest:request error:&error];
return result;
}
So i am not getting the data in same order as i inserted.

You have to use [NSFetchRequest setSortDescriptor:] to get a meaningful ordering of your results. The NSFetchRequest documentation doesn't say anything about the default order of the results, so it's not a good idea to assume there is any.
Of course, in order to correctly specify the sort descriptor, you probably need to add a field to your managed objects to sort on, and assign a value to it when creating the objects. It could be an incrementing index field, a creation date, or something like that.

Related

Core Data: does a fetch have to make a trip to persistent store?

Say I do this:
NSManagedObjectContext *context = #a managed object context";
NSString *entityName = #an entity name#;
NSFetchRequest *requestForAll = [NSFetchRequest requestWithEntityName:entityName];
NSArray *allObj = [context executeFetchRequest:requestForAll];
for (NSString *name in allNamesArray){
NSFetchRequest *requestForOne = [NSFetchRequest requestWithEntityName:entityName];
requestForOne.predicate = [NSPredicate predicateWithFormat:#"name == %#",name];
NSArray *ObjsWithName = [context executeFetchRequest:requestForOne];
#do some work with the obj#
}
Does the fetch in the loop incur a trip to the persistent store every time? Or those fetches will only be performed in coredata's row cache?
EDIT
I've written a fragment of testing code :
You need to create a core data entity named "Person" and it should have an attribute named "name", which is of type string.
use this code to populate some data:
self.array = #[#"alkjsdfkllaksjdf",#"asldjflkajdklsfjlk;aj",#"aflakjsdl;kfjalksdjfklajkldhkl;aj",#"aljdfkljalksdjfl;j" ,#"flajdl;kfjaklsdjflk;j",#"akldsjfklajdslkf",#"alkdjfkljaklsdjflkaj",#"alsdjflkajsdflj",#"adlkfjlkajsdfkljkla",#"alkdjfklajslkdfj"];
NSString *firstRunKey = #"oh its first run!";
NSString *firstRun = [[NSUserDefaults standardUserDefaults] objectForKey:firstRunKey];
if (!firstRun) {
for (NSString *name in self.array) {
Person *p = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
p.name = name;
}
}
[self.managedObjectContext save];
[[NSUserDefaults standardUserDefaults] setObject:firstRunKey forKey:firstRunKey];
[[NSUserDefaults standardUserDefaults] synchronize];
profile this two methods and you'll find usingCoreData costs much more time than usingFilterArray!
static int caseCount = 1000;
-(void)usingCoreData
{
NSLog(#"core data");
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Person"];
NSArray *allPersons = [self.managedObjectContext executeFetchRequest:request error:nil];
for (int i = 0; i < caseCount; i++){
for (NSString *name in self.array) {
request.predicate = [NSPredicate predicateWithFormat:#"name == %#",name];
NSArray *result = [self.managedObjectContext executeFetchRequest:request error:nil];
}
}
}
-(void)usingFilterArray
{
NSLog(#"filter array");
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Person"];
NSArray *allPersons = [self.managedObjectContext executeFetchRequest:request error:nil];
for (int i = 0; i < caseCount; i++){
for (NSString *name in self.array) {
NSArray *array = [allPersons filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"name == %#",name]];
}
}
}
Guess I need to answer my question myself.
I tested it and found, every time a fetch executed, core data will translate your NSFetchRequest into SQL command and invoke a data base query,the query result is firstly NSManagedObjectIDs, caching is applied to get the NSManagedObject from a NSManagedObjectID.
In conclusion, it caches object, but doesn't cache query result.
That means you execute the same NSFetchRequest for 10 times, it will query your persistent store for 10 times, event though you will get 10 times the same result. So in such situation, filtering array in memory will perform better than fetching.
The fetch will come from the specified cache when available.
EDIT:
Here's a link to a great tutorial that shows how to set up a NSFetchedResultsController that uses a cache.
http://www.raywenderlich.com/?p=999

Float became 0.0000 when fetched from core data

I created an entity with a float attribute and set it's value to 0.5
+(void)createSportNamed:(NSString *)name withContext:(NSManagedObjectContext *)context{
Sport *newSport = [NSEntityDescription insertNewObjectForEntityForName:#"Sport" inManagedObjectContext:context];
newSport.name = name;
newSport.maxRep = [NSNumber numberWithInt:45];
newSport.endurance = [NSNumber numberWithFloat:0.5f];
NSLog(#"endurance at set = %f",[newSport.endurance floatValue]);
NSError *saveError = nil;
[context save:&saveError];
}
From the log, the value of the float is still 0.5000
But when I fetch it later on, the value somehow became 0.0000
-(NSArray*)createWorkoutForSport:(NSString*)sportName withContext:(NSManagedObjectContext*)context{
NSFetchRequest *request = [NSFetchRequest new];
request.entity = [NSEntityDescription entityForName:#"Sport" inManagedObjectContext:context];
NSPredicate *sportNamePredicate = [NSPredicate predicateWithFormat:#"name == %d",sportName];
request.predicate = sportNamePredicate;
NSError *err = nil;
NSArray *results = [context executeFetchRequest:request error:&err];
Sport *theFetchedSport = [results lastObject];
int totalRep = [[theFetchedSport maxRep]intValue];
float endure = [[theFetchedSport endurance]floatValue];
int set;
NSLog(#"Endurance = %f",endure);
+(void)createSportNamed:(NSString *)name withContext:(NSManagedObjectContext *)context{
Sport *newSport = [NSEntityDescription insertNewObjectForEntityForName:#"Sport" inManagedObjectContext:context];
newSport.name = name;
This clearly shows that the "name" field of your Sport entity is a string.
[NSPredicate predicateWithFormat:#"name == %d",sportName]
And yet here you are using %d, which means int. You're casting a string pointer to an int. You should be using %#. Thus, your fetch request is returning an empty array, and -lastObject is returning nil, which means that [nil endurance] is also nil, which means that [[nil endurance] floatValue] is also nil, or 0.

Error: NSArray was mutated while being enumerated thrown when accessing globals and Core Data

I have this piece of code which I am using to update some values in Core Data when another object is added:
//Create new receipt
Receipt *receipt = [[Receipt alloc] init];
receipt.project = self.projectLabel.text;
receipt.amount = self.amountTextField.text;
receipt.descriptionNote = self.descriptionTextField.text;
receipt.business = self.businessNameTextField.text;
receipt.date = self.dateLabel.text;
receipt.category = self.categoryLabel.text;
receipt.paidBy = self.paidByLabel.text;
receipt.receiptImage1 = self.receiptImage1;
//Need to set this to 2
receipt.receiptImage2 = self.receiptImage1;
receipt.receiptNumber = #"99";
int count = 0;
int catCount = 0;
for (Project *p in appDelegate.projects)
{
if ([p.projectName isEqualToString:receipt.project]){
double tempValue = [p.totalValue doubleValue];
tempValue += [receipt.amount doubleValue];
NSString *newTotalValue = [NSString stringWithFormat:#"%.02f", tempValue];
NSString *newProjectName = p.projectName;
//remove entity from Core Data
NSFetchRequest * allProjects = [[NSFetchRequest alloc] init];
[allProjects setEntity:[NSEntityDescription entityForName:#"Project" inManagedObjectContext:appDelegate.managedObjectContext]];
[allProjects setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * projectsArray = [appDelegate.managedObjectContext executeFetchRequest:allProjects error:&error];
//Delete product from Core Data
[appDelegate.managedObjectContext deleteObject:[projectsArray objectAtIndex:count]];
NSError *saveError = nil;
[appDelegate.managedObjectContext save:&saveError];
[appDelegate.projects removeObjectAtIndex:count];
NSLog(#"Removed project from Core Data");
//Insert a new object of type ProductInfo into Core Data
NSManagedObject *projectInfo = [NSEntityDescription
insertNewObjectForEntityForName:#"Project"
inManagedObjectContext:appDelegate.managedObjectContext];
//Set receipt entities values
[projectInfo setValue:newProjectName forKey:#"name"];
[projectInfo setValue:newTotalValue forKey:#"totalValue"];
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Added Project to Core Data");
Project *tempProject = [[Project alloc] init];
tempProject.projectName = [projectInfo valueForKey:#"name"];
tempProject.totalValue = [projectInfo valueForKey:#"totalValue"];
[appDelegate.projects addObject:tempProject];
}
count++;
}
for (Category *c in appDelegate.categories){
if ([c.categoryName isEqualToString:receipt.category]){
double tempValue = [c.totalValue doubleValue];
tempValue += [receipt.amount doubleValue];
NSString *newTotalValue = [NSString stringWithFormat:#"%.02f", tempValue];
NSString *newCategoryName = c.categoryName;
//remove entity from Core Data
NSFetchRequest * allCategories = [[NSFetchRequest alloc] init];
[allCategories setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:appDelegate.managedObjectContext]];
[allCategories setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * categoriesError = nil;
NSArray * categoriesArray = [appDelegate.managedObjectContext executeFetchRequest:allCategories error:&categoriesError];
//Delete product from Core Data
[appDelegate.managedObjectContext deleteObject:[categoriesArray objectAtIndex:catCount]];
NSError *categorySaveError = nil;
[appDelegate.managedObjectContext save:&categorySaveError];
[appDelegate.categories removeObjectAtIndex:catCount];
NSLog(#"Removed category from Core Data");
NSError * error = nil;
//Insert a new object of type ProductInfo into Core Data
NSManagedObject *categoryInfo = [NSEntityDescription
insertNewObjectForEntityForName:#"Category"
inManagedObjectContext:appDelegate.managedObjectContext];
//Set receipt entities values
[categoryInfo setValue:newCategoryName forKey:#"name"];
[categoryInfo setValue:newTotalValue forKey:#"totalValue"];
if (![appDelegate.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Added Category to Core Data");
Category *tempCategory = [[Category alloc] init];
tempCategory.categoryName = [categoryInfo valueForKey:#"name"];
tempCategory.totalValue = [categoryInfo valueForKey:#"totalValue"];
[appDelegate.categories addObject:tempCategory];
}
catCount++;
}
This code gives the error:
'...was mutated while being enumerated'.
Can anyone explain why? Also, is there a better approach to doing what I'm trying to achieve?
The error you're seeing is accurate. The problem is that you're mutating (changing) a collection while iterating over it. Basically, you're doing something of the form:
for (Project *p in appDelegate.projects) {
...
[p addObject: X]
}
This isn't allowed.
One simple solution is to make a new collection of the objects you want to add, and then add them to the original container outside of your loop. Something like:
NSMutableArray *array = [NSMutableArray array];
for (Project *p in appDelegate.projects) {
...
[array addObject:X];
}
[p addObjects:array];
By the way, did you google for the error text "was mutated while being enumerated"? I'd be surprised if you didn't find the answer for this common problem just by googling.
Also, when posting an error message, it's helpful to post the full line, not just part of it.
You are adding and removing items from appDelegate.projects while iterating over it in a for-each loop here:
[appDelegate.projects removeObjectAtIndex:count];
// ...
[appDelegate.projects addObject:tempProject];
And the same for appDelegate.categories:
[appDelegate.categories removeObjectAtIndex:count];
// ...
[appDelegate.categories addObject:tempProject];
AFAIK, in such case you should use a simple for loop, and access the array with index.

Count number of entries from NSFetchRequest

I have a method which is void but I have to count the number of entries in it.
So is there any way I can do it??
I tried to implement it but its not working.
It's giving me a error at Nsfetch count.
The return is declared as null.
testcase1
{
//startdate and end date are same and both are 1 day before
NSTimeInterval secondsPerday=24*60*60;
NSDate *startdate = [[NSDate alloc]initWithTimeIntervalSinceNow:-secondsPerday];
NSDate *endate = [[NSDate alloc]initWithTimeIntervalSinceNow:-secondsPerday];
TravelerAppDelegate *delegate =[[UIApplication sharedApplication]delegate];
[delegate getWeatherforCity:#"#" state:#"#" country:#"#" startDate:startdate endDate:endate];
NSManagedObjectContext *allone=[delegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Weather" inManagedObjectContext:allone];
//WeatherXMLParser *delegate = [[WeatherXMLParser alloc] initWithCity:#"#" state:#"#" country:#"#"];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Weather" inManagedObjectContext:allone]];
[request setIncludesSubentities:NO];
NSError *err;
NSUInteger count = [allone countForFetchRequest:request error:&err];
if(count == NSNotFound) {
//Handle error
}
[request release];
return count;
}
First, you define the variable entity but when you set the entity of the fetch request, you are doing the same as above. Simply write:
[request setEntity:entity];
Then, NSFetchRequest has to have a sort descriptor array. So, add this:
[request setSortDescriptors:[NSArray array]];
If you want a method to return something, then set its return type.
-(void)testcase1
{
// Code goes here
}
Nothing (void) is returned. You'd call a method like this by:
[self testcase1];
To return something:
-(NSInteger)testcase1
{
// Code goes here
return count;
}
You would call such a method like this:
NSInteger count = [self testcase1];

CoreData - Adding a new relationship to existing entity

I have the following basic problem:
I have two entities, person and department.
Before adding a new person I want to check, that the department does not already exists and if so, then link the new person to the existing department.
Simple insert with a new department relationship:
Department *department = (Department *)[NSEntityDescription insertNewObjectForEntityForName:#"Department" inManagedObjectContext:self.context];
department.groupName = #"Office of Personnel Management";
Person *person1 = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.context];
person1.name = #"John Smith";
person1.birthday = [self dateFromString:#"12-1-1901"];
person1.department = department;
Person *person2 = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.context];
person2.name = #"Jane Doe";
person2.birthday = [self dateFromString:#"4-13-1922"];
person2.department = department;
department.manager = person1;
department.members = [NSSet setWithObjects:person1, person2, nil];
the last line makes the linkage - that's ok.
But what if I want to do the following, after the execution of the code above:
[self checkForExistingDepartment:#"Office of Personnel Management"];
if(self.existingDepartment) {
// here is my Problem number 1:
// department = ???
(NSEntityDescription *) department = self.existingDepartment;
} else {
Department *department = (Department *)[NSEntityDescription insertNewObjectForEntityForName:#"Department" inManagedObjectContext:self.context];
department.groupName = #"Office of Personnel Management";
}
Person *person1 = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.context];
person1.name = #"John Smith the second one";
person1.birthday = [self dateFromString:#"12-1-1901"];
person1.department = department;
// former line for adding new: department.members = [NSSet setWithObjects:person1, nil];
// and here is my problem number 2:
// I think I need something like: [NSSet addObjects:person1, nil];
In short form my problem are duplicated entries in table department.
Perhaps someone knows a good CoreData tutorial which is good for beginner with advanced SQL knowledge. (Searching on google or reading the developer documentation for hours is not that helpful as I thought :) )
For me as a beginner it's important to now whether I'm on the right way or not, can anybody confirm this?
Thanks and greetings,
matthias
You're defining the department variable inside the if/else statement and then using a different one outside of it. Try changing your if to look like :
[self checkForExistingDepartment:#"Office of Personnel Management"];
if(self.existingDepartment) {
department = self.existingDepartment;
} else {
department = (Department *)[NSEntityDescription insertNewObjectForEntityForName:#"Department" inManagedObjectContext:self.context];
department.groupName = #"Office of Personnel Management";
}
Though without seeing the code for checkForExistingDepartment we can't help you very much . . .
Sorry, the check function looks like this:
- (void) checkForExistingDepartment:(NSString *)searchDepartment {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Department" initManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"country" ascending:YES selector:nil];
NSArray *descriptors = [NSArray arrayWithObject:sortDescriptor];
[fetchRequest setSortDescriptor:descriptors];
NSString *query = searchDepartment;
if(query && query.length) {
fetchRequest.predicate = [NSPredicate predicatWithFormat:#"country contains[cd] %#", query];
}
NSError *error;
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.context
sectionNameKeyPath:#"section"
cacheName:#"Root"];
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController release];
if(![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Error %#", [error localizedDescription]);
}
self.existingDepartment = [self.fetchedResultsController objectAtIndexPath:0];
[fetchRequest release];
[sortDescriptor release];
}