In my previous articles on Core Data, I frequently offered comparisons back to MySQL, since that's the environment that I presume many readers will be coming from. It's a great starting place, but as you come to understand Core Data better, you may find that there are cleaner ways to do certain types of lookups. I won't promise they're more efficient, since that falls upon the backend's coding, but they will often better map the abstracted objects that you've created in your Core Data projects.
To exemplify what I mean I'm going to offer a few examples using the card game model that I outlined in part two of this series. If you look at that model you'll see that I have a "CardStack" object (which could be a deck, a draw pile, or a hand) which contains a number of "Card" objects. This connection is maintained by a one-to-many relationship called "cards" in the CardStack object and by a one-to-one relationship called "location" in the Card object.
This article will show how using those relationships can offer cleaner ways to access linked data than doing new Core Data lookups.
Two Ways to Lookup
Coming from an SQL background, the most obvious way to list the cards in the deck is something like this:
-(NSArray *)contentsOfHand:(CardStack *)thisDeck
sortBy:(NSArray *)sorting {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescriptionentityForName:@"Card"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];NSPredicate *predicate = [NSPredicate
predicateWithFormat:@"location=%@",thisDeck];
[fetchRequest setPredicate:predicate];if (sorting) {
NSMutableArray *sortArray = [NSMutableArrayarrayWithCapacity:[sorting count]];
for (int i = 0; i < [sorting count]; i++) {
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:[sorting objectAtIndex:i] ascending:YES];
[sortArray addObject:sort];
[sort release];
}[fetchRequest setSortDescriptors:sortArray];
}NSError *error;
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
return items;
}
This should look familiar because it's almost identical to the retrieval example I gave in part three of this Core Data series. You create a fetch request for the Card entity, then make a predicate that limits lookups to the correct Deck location, and then sort it as you see fit.
However, someone more familiar with Core Data would probably take the opposite tactic, beginning with the Deck instead of searching through the Cards.
Here's what that would look like:
NSArray *theseCards = [[thisDeck cards] allObjects];
The "cards" relationship lookup generates an NSSet and the allObjects method call turns that into an array. Seems a bit easier, doesn't it? That's the power of relationships in Core Data.
However, there's a catch too: as I said, when you retrieve a one-to-many relationship, you get back an NSSet, and NSSets are innately unordered. There are ways around this, which I'm going to talk about in an upcoming article (or two) about NSSets, but the end result is that when you care more about ordering, you may want to do a direct retrieval from Core Data, rather than using a relationship.
Neither of my examples uses a predicate to further limit a lookup. You can use an NSPredicate to limit either a Core Data lookup or an NSSet directly, but again those are going to be topics for future articles.
Two Ways to Count
I think that in the previous example it's a pretty clear win to use the relationship reference rather than the Core Data lookup if you don't want to do any sorting. The code is shorter, it's clearer, and I could even attach it directly to my "CardStack" object if I wanted, to maintain an easy, well abstracted method for retrieving an NSArray of a deck's cards.
However, I've already admitted there are situations where it might not be optimal. Before I close out this article, I want to offer another example that's an even clearer win: getting a count of one of my Core Data decks.
Here's what's probably the most exhaustive way to do it, using Core Data:
-(int)countCardsInDeck:(CardStack *)thisDeck {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescriptionentityForName:@"Card"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];NSPredicate *predicate = [NSPredicate
predicateWithFormat:@"location=%@",thisDeck];
[fetchRequest setPredicate:predicate];NSError *error;
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];return [items count];
}
If you're now thinking about the "Core Data" way to do things, as opposed to the "SQL" way, you might already have the alternative method, which I offer here as an instance method in the CardStack object:
-(int)count {
return [self.cards count];
}
As we've already seen, a one-to-many relationship returns a NSSet. "count" is a method call that's supported by that NSSet. In this situation I suspect there's no reason to ever go with the more cumbersome SQL-influenced Core Data call.
Conclusion
In my series on Core Data thus far I've encouraged an understanding of it based on SQL. Thinking about rows, columns, and tables make it easy to get started with Apple's data framework. However, as you delve further into Core Data, you should think about the relationships that you can create between objects. They aren't just superfluous modeling details; they're real elements that you can use to make your usage of Core Data simpler.
