Cocoa Core Data - Efficient Related Entities Counts - iphone

I am working on my first iPhone application and I've hit a wall. I'm trying to develop a 'statistics' page for a three entity relationship. My entities are the following:
Department - Name, Address, Building, etc.
People - Name, Gender (BOOL), Phone, etc
If I have fetched a specific department how do I filter those results and only return people that are Male (Gender == 0)?
If I do
NSLog(#"%d", [department.people count]);
I get the correct number of people in that department so I know I'm in the neighborhood. I know I could re-fetch and modify the predicate each time but with 20+ stats in my app that seems inefficient. Thanks for any advice!

You don't need to refetch:
NSPredicate* pred = [NSPredicate predicateWithFormat:#"gender == NO"];
NSUInteger count = [[department.people filteredArrayUsingPredicate:pred] count];
NSLog(#"%lu", (unsigned long)count);
Somehow gender==NO still looks strange though ;)
If copying is too expensive, you could use enumerators instead. E.g.:
NSUInteger CountIf(NSEnumerator* en, NSPredicate* pred) {
NSUInteger count = 0;
id obj;
while (obj = [en nextObject]) {
if([pred evaluateWithObject:obj])
++count;
}
return count;
}
NSUInteger count = CountIf([department.people objectEnumerator], predicate));
... though this would be ideally moved to a suitable category as say countOfObjectsMatchingPredicate:.

You could create NSPredicates representing your different filters and use NSSet's filteredSetWithPredicate: method. The count method will give you the number of entities matching the predicate. This isn't terribly efficient because you're creating a new set for each calculation, but it may be significantly faster than fetching each time.

Related

which technique will be very speed among these three techniques? [duplicate]

This question already has answers here:
Objective C — What is the fastest and most efficient way to enumerate an array?
(3 answers)
Closed 8 years ago.
NSEnumerator* friendsEnumerator = [friends objectEnumerator];
id aFriend;
while ((aFriend = [friendsEnumerator nextObject])) {
printf("%s\n", [aFriend UTF8String]);
}
int friendsCount = [friends count];
for(int i = 0; i < friendsCount; i++) {
printf("%s\n", [[friends objectAtIndex: i] UTF8String]);
}
for(NSString* aFriend in friends) {
printf("%s\n", [aFriend UTF8String]);
}
Case 3 is the fastest and generally better approach:
Fast enumeration is the preferred method of enumerating the contents of a collection because it provides the following benefits:
The enumeration is more efficient than using NSEnumerator directly.
List item
The syntax is concise.
The enumerator raises an exception if you modify the collection while enumerating.
You can perform multiple enumerations concurrently.
You can read more about it here
You can enumerate an array using the below method also.The stop parameter is important for performance, because it allows the enumeration to stop early based on some condition determined within the block.
[friends enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop){
if ('some condition') {
NSLog(#"Object Found: %# at index: %i",obj, index);
*stop = YES;
}
} ];
First things first: option 1 and 3 are the same in terms of operations, both use the NSFastEnumeration protocol to provide a fast access to objects in collections.
As the name "NSFastEnumeration" implies, enumerations are faster then for-loops, as they don't need array bounds checking for each single object.
So it comes down to a matter of taste between 1 and 3. Personally I prefer forin-loops as they seem to be more elegant.

Add object to sorted NSMutable array and answer index path

I have a sorted mutable array of a class called Topic. The topics represent a an array of Publications. I present the topics in a table, and periodically fetch new publications from a web service. When a new publication arrives, I'd like to add to the table with an animation.
What's bothering me is the computational work I need to do to add into this array, and answer the correct index path. Can someone suggest a more direct way than this:
// add a publication to the topic model. if the publication has a new topic, answer
// the index path of the new topic
- (NSIndexPath *)addPublication:(Publication *)pub {
// first a search to fit into an existing topic
NSNumber *topicId = [pub valueForKey:#"topic_id"];
for (Topic *topic in self.topics) {
if ([topicId isEqualToNumber:[topic valueForKey:"id"]]) {
// this publication is part of an existing topic, no new index path
[topic addPublication:pub];
return nil;
}
}
// the publication must have a new topic, add a new topic (and therefore a new row)
Topic *topic = [[Topic alloc] initWithPublication:publication];
[self.topics addObject:topic];
// sort it into position
[self.topics sortUsingSelector:#selector(compareToTopic:)];
// oh no, we want to return an index path, but where did it sort to?
// yikes, another search!
NSInteger row = [self.topics indexOfObject:topic];
return [NSIndexPath indexPathForRow:row inSection:0];
}
// call this in a loop for all the publications I fetch from the server,
// collect the index paths for table animations
// so much computation, poor user's phone is going to melt!
There's no getting around the first search, I guess. But is there some more efficient way to add a new thing to an array, maintaining a sort and remembering where it got placed?
It's pretty straightforward to insert a value into a sorted list. Think about how you would insert the number "3" into the list "1, 2, 7, 9", for instance. You want to do exactly the same thing.
Loop through the array by index, using a for loop.
For each object, use compareToTopic: to compare it to the object you want to insert.
When you find the appropriate index to insert at, use -[NSArray insertObject:atIndex:] to insert it.
Then return an NSIndexPath with that index.
Edit: and, as the other answers point out, a binary search would be faster -- but definitely trickier to get right.
This is almost certainly not an issue; NSArrays are actually hashes, and search is a lot faster than it would be for a true array. How many topics can you possibly have anyways?
Still, if you measure the performance and find it poor, you could look into using a B-tree; Kurt Revis commented below with a link to a similar structure (a binary heap) in Core Foundation: CFBinaryHeap.
Another option (which would also need to be measured) might be to do the comparison as you walk the array the first time; you can mark the spot and do the insertion directly:
NSUInteger insertIndex = 0;
NSComparisonResult prevOrder = NSOrderedDescending;
for (Topic *topic in self.topics) {
NSComparisonResult order = [topicId compareToTopic:topic];
if (NSOrderedSame == order) {
// this publication is part of an existing topic, no new index path
[topic addPublication:pub];
return nil;
}
else if( prevOrder == NSOrderedDescending &&
order == NSOrderedAscending )
{
break;
}
insertIndex++;
prevOrder = order;
}
Please note that I haven't tested this, sorry.
I'm not sure this is actually better or faster than the way you've written it, though.
Don't worry about the work the computer is doing unless it's demonstrably doing it too slowly.
What you have done is correct I guess. There's another way. You can write your own binary search implementation method. (Which has only few lines of code). And you can retrieve the index where the new object should fit in. And add the new object to the required index using insertObject:atIndex: method.

How to Efficiently Reset Attributes in a Core Data Entity

My app uses Core Data and has an attribute called 'beenSeen'. When a user refreshes the app, all 'beenSeen' values of 1 are changed to 0. On an iPod Touch 2nd gen with over 2000 objects, refreshing takes over a minute. My code looks like this:
for (Deck *deck in self.deckArray) {
if ([deck.beenSeen isEqualToNumber:[NSNumber numberWithInt:1]]) {
[deck setBeenSeen:[NSNumber numberWithInt:0]];
[self.managedObjectContext save:&error];
}
}
I'm also considering deleting the sqlite file and having an alert ask the user to restart the app themselves. Doing that sure is a whole lot quicker than what I have now. Is there a quicker way to refresh an entity? Could I have a 'backup' entity and copy it over? Thanks for any help.
Hm. The first optimization I'd suggest would be
for (Deck *deck in self.deckArray) {
if ([deck.beenSeen isEqualToNumber:[NSNumber numberWithInt:1]]) {
[deck setBeenSeen:[NSNumber numberWithInt:0]];
}
}
[self.managedObjectContext save:&error];
I suspect it might speed things up to do one big context save, instead of 2,000 little ones.
The second suggestion would be to try getting rid of the if test – if the majority of your beenSeen values are changing from 1 to 0, and the others are already 0, then you might as well just set all of them to 0 and save the time of checking each one individually. (On the other hand, if there are 10,000 objects and you're resetting 2,000 of them, then getting rid of the test might not be optimal.)
for (Deck *deck in self.deckArray) {
[deck setBeenSeen:[NSNumber numberWithInt:0]];
}
[self.managedObjectContext save:&error];
}
The third suggestion would be to think about implementing this another way – for instance, your deck object could implement a lastSeen attribute, storing the date and time when the deck was last seen, and then instead of doing a mass reset (and writing 2,000 Core Data rows) you could just test each deck's lastSeen date and time against the timestamp of the last user refresh.
Try this, First, filter the array using a predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"beenSeen == %#",
[NSNumber numberWithInt:1]];
NSArray* filtered = [self.deckArray filteredArrayUsingPredicate:predicate];
Now set the new value:
[filtered setValue:[NSNumber numberWithInt:0] forKeyPath:#"beenSeen"];
Finally save the context:
[self.managedObjectContext save:&error];
Hope this helps :)

Searching for an id in Core Data

I am getting some unexpected behavior with CoreData and NSPredicate. In a large database population I have different Managed Objects relating to one-another. However, I have a problem with the following. When giving an id (NSNumber, given as NSString to this function) I don't get a result unless I save the whole context first. I don;t want to do that as it takes too much time (as it is a large set of data). The code is:
- (DOSite *) findSite:(NSString *) siteId {
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"(id = %#)", siteId];
[NSFetchedResultsController deleteCacheWithName:nil];
[[self fetchedResultsController].fetchRequest setPredicate:predicate];
NSError *fetchError;
if (![[self fetchedResultsController] performFetch:&fetchError]) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
NSLog(#"Fetching data error: %#", [fetchError localizedDescription]);
}
if([[[self fetchedResultsController] fetchedObjects] count] == 0){
return NULL;
}
return (DOSite *)[[[self fetchedResultsController] fetchedObjects] objectAtIndex:0];
}
So when I add an x number of items (using +[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:]) doing a search on all items return the right amount of items.
When searching for a string (e.g.predicateWithFormat:#"(name LIKE %#)") I get positive results, but when using the above code predicateWithFormat:#"(id = %#) I get zero results.
The only way I can get results is to save the whole context and then perform the fetchRequest, then suddenly it works.
So there must be something small I do wrong in searching for the id, I just seem to be blind to find it and spend two days at it now to narrow it down to this point. Is there anybody who can give me some advice on this?
This may not work, but have you tried using a name more complex than "id" in your entity (like "SiteID")? Sometimes very short names overlap with other system properties and it causes odd issues.
The problem was that I gave a NSString to the predicate as outlined above. When changing that to an int (ie predicateWithFormat:#"(id == %i)") it works fine for some reason.

Bulk update & occasional insert (coredata) - Too slow

Update: Currently looking into NSSET's minusSet
links: Comparing Two Arrays
Hi guys,
Could benefit from your wisdom here..
I'm using Coredata in my app, on first launch I download a data file and insert over 500 objects (each with 60 attributes) - fast, no problem.
Each subsequent launch I download an updated version of the file, from which I need to update all existing objects' attributes (except maybe 5 attributes) and create new ones for items which have been added to the downloaded file.
So, first launch I get 500 objects.. say a week later my file now contains 507 items..
I create two arrays, one for existing and one for downloaded.
NSArray *peopleArrayDownloaded = [CoreDataHelper getObjectsFromContext:#"person" :#"person_id" :YES :managedObjectContextPeopleTemp];
NSArray *peopleArrayExisting = [CoreDataHelper getObjectsFromContext:#"person" :#"person_id" :YES :managedObjectContextPeople];
If the count of each array is equal then I just do this:
NSUInteger index = 0;
if ([peopleArrayExisting count] == [peopleArrayDownloaded count]) {
NSLog(#"Number of people downloaded is same as the number of people existing");
for (person *existingPerson in peopleArrayExisting) {
person *tempPerson = [peopleArrayDownloaded objectAtIndex:index];
// NSLog(#"Updating id: %# with id: %#",existingPerson.person_id,tempPerson.person_id);
// I have 60 attributes which I to update on each object, is there a quicker way other than overwriting existing?
index++;
}
} else {
NSLog(#"Number of people downloaded is different to number of players existing");
So now comes the slow part.
I end up using this (which is tooooo slow):
NSLog(#"Need people added to the league");
for (person *tempPerson in peopeArrayDownloaded) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person_id = %#",tempPerson.person_id];
// NSLog(#"Searching for existing person, person_id: %#",existingPerson.person_id);
NSArray *filteredArray = [peopleArrayExisting filteredArrayUsingPredicate:predicate];
if ([filteredArray count] == 0) {
NSLog(#"Couldn't find an existing person in the downloaded file. Adding..");
person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:#"person" inManagedObjectContext:managedObjectContextPeople];
Is there a way to generate a new array of index items referring to the additional items in my downloaded file?
Incidentally, on my tableViews I'm using NSFetchedResultsController so updating attributes will call [cell setNeedsDisplay];
.. about 60 times per cell, not a good thing and it can crash the app.
Thanks for reading :)
I'll begin by saying that I'm still new to using the Core Data framework, but my guess is that your problem lies in the for loop you've posted.
If you look at your loop, each time it executes it creates a new NSPredicate object and then filters your existing array looking for matches. On a small data set this technique would work with seemingly small performance losses; however, with your large data set you will end up spending a lot of time creating NSPredicate objects that only differ in the name you've provided. I would suggest that you look at how to create a single predicate and then use variable substitution to perform the search. For information about variable use in predicates check out: http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174
As a side note, you may also consider how you've sorted your data and how you are performing the search operation. And another thing I noticed is that you don't release your NSPredicate object, so you're just tossing memory away too.