Defining a NSFetchedResultsController's scope? - ios

Let's say I have a Core Data model, with a graph that looks like this:
Book->Chapter->Page
and I want to pull up some Pages. Is there a way to restrict a NSFetchedResultsController's scope to the contents of the "pages" to-many relationship (NSSet) of a given Chapter?
One workaround way is with a predicate (only return Pages whose chapter inverse matches the Chapter I want), but won't the fetch have to search through ALL the Page objects in the store? Seems like it'd be better to just tell the fetch "only work with items in this NSSet".
Caching is out of the question. Too many horror stories...
Any ideas? Thanks! :)

There is only one way to find out what the result would be.
It's to do it and profile / test it.
If you are using an NSFetchedResultsController, do the predicate where chapter matches, as well as the book name.
If you are concerned about efficiency, make sure that the key you are fetching / predicating are indexed.
If the result is to slow, that will be the time to think about optimization.

Related

Fastest way to find an entity in CoreData

I have 20k unique labels that each have their own Entity with their own title.
What is the quickest way to get access to an Entity, given its title?
I know this can be done using a predicate, like so
fetch.predicate = NSPredicate(format: "title contains %#", "example title")
My issue with this approach is that it involves searching through every single one of the 20k Entities until the right one is found.
Is there a way to do this where all the titles are somehow indexed, and I can instantly get access to any label? Similar to how you can instantly get access to an item in an associative array, with array['item_name'].
Thanks.
Using a predicate is how you do it with Core Data.
Before you do anything, is this actually a problem? You don't mention that you've seen any performance issues. Are you having any, or is this still a theoretical problem?
You might improve performance by making sure that the "Indexed" box is checked for this attribute in the Core Data model editor. You might also consider adding a field to your entity that would contain a numeric hash of the title, and fetching based on the hash. You'd still be searching every entity but you'd be doing numeric comparisons instead of strings. You wouldn't be able to do substring searches (as your use of contains implies) but you would be doing the same thing as an associative array (which is also called a hash, for exactly this reason).
If none of that works well enough and you're searching strings very frequently, you'll need to investigate a different data model more suited to your needs-- like building a trie structure for fast searching.

CoreData getting objects based on a distinct property

I've had a problem for a while and I have hacked together a solution but I am revisiting it in the hopes of finding a real solution. Unfortunately that is not happening. In Core Data I've got a bunch of RSS articles. The user can subscribe to individual channels within a single feed. The problem is that some feed providers post the exact same article in multiple channels of the same feed. So the user ends up getting 2+ versions of the same article. I want to keep all articles in case the user unsubscribes from a channel that contains one copy but stays subscribed to another channel with a duplicate, but I only want to show a single article in the list of available articles.
To identify duplicates, I create a hash value of the article text content and store it as a property on the Article entity in Core Data (text_hash). My original thinking was that I would be able to craft a fetch request that could get the articles based on a unique match on this property, something like an SQL query. That turns out not to be the case (I was just learning Core Data at the time).
So to hack up a solution, I fetch all the articles, I make an empty set, i enumerate the fetch results, checking if the hash is in the set. If it is, I ignore it, if it isn't, i add it to the set and I add the article id to an array. When I'm finished, I create a predicate based on the article ids and do another fetch.
This seems really wasteful and clumsy, not only am i fetching twice and enumerating the results, since the final predicate is based on the individual article ids, I have to re-run it every time I add a new article.
It works for now but I am going to work on a new version of this app and I would like to make this better if at all possible. Any help is appreciated, thanks!
You could use propertiesToGroupBy like so:
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"Article"];
fr.propertiesToGroupBy = #[#"text_hash"];
fr.resultType = NSDictionaryResultType;
NSArray *articles = [ctx executeFetchRequest:fr error:nil];

Display flattened tree structure with NSFetchedResultsController

I'm retrieving what is essentially an album structure.
Albums can have a parent album and children albums. All the relationships will already be setup in core data.
I'm trying to figure out a way that I can display these with NSFRC. They will appear to look like a tree (they also have a depth property which I'll use to format how far they are indented in each cell), but in reality are just a flat list.
Essentially I want something like this:
|-album1
|-album2
|--subalbum1
|--subalbum2
|---subsubalbum3
|-album3
|--subalbum3
Not sure if there is a way to solve this with sorting, etc in NSFRC, at least not that my brain has come up with. Maybe a transient property on each album that references it's root album?
If you're using an sqlite store and you want to use NSFetchedResultsController you'll need to implement a "nested set" model [1]. Implementation is a little complicated though, so if your dataset is small enough to fit into memory it'll probably be easier to not use NSFetchedResultsController and do the sorting manually.
[1] http://en.wikipedia.org/wiki/Nested_set_model

Correct way to accessing attributes of a class modeled in core data

Let's say a Recipe object has an NSSet of one or more Ingredients, and that the same relationship is modeled in core data.
Given a recipe, what id the correct way to access its ingredients?
In this example it seems natural to use recipe.ingredients, but I could equally use an NSFetchRequest for Ingredient entities with an NSPredicate to match by recipe.
Now let's say I want only the ingredients that are 'collected'. This is less clear cut to me - should I use a fetch request for ingredients with a predicate restricting by recipe and collected state? Or loop through recipe.ingredients?
At the other end of the scale, perhaps I need only ingredients from this recipe that also appear in other recipes. Now, the fetch request seems more appealing.
What is the correct general approach? Or is it a case by case scenario? I am interested in the impact on:
Consitancy
Readability
Performance
Robustness (for example, it is easy to make an error in a fetch request that the compiler cannot catch).
Let's go through these in order.
Getting the ingredients for a specific Recipe, when you already have a reference: Use recipe.ingredients every time.
Getting the ingredients for a specific Recipe that have a specific value (e.g. a Boolean flag value): Easiest is probably to start with recipe.ingredients as above and then use something like NSSet's objectsPassingTest to filter them. Most elegant is to set up a fetched property on Recipe that just returns these ingredients with no extra code (the syntax may not be immediately obvious, see a previous answer I wrote for details). These two probably perform about equally. Least appealing is a fetch request.
Getting ingredients that appear in multiple recipe instances: Probably a fetch request for the Ingredient entity where the predicate is something like recipe in %#, and the %# is replaced by a list of Recipe instances.
Some basic info:
*memory operations are ~100-1000 times faster then disk operations.
*A fetch request execution is always a trip to the store (disk), and so, degrade performance.
In your case, you have a "small" set of objects that need to be queried for information.
simply iterating over them using the recipe.ingredients set would fault them one by one, each access will be a trip to the store (fault resolution).
In this case, use prefetching (either in the request, set the setRelationshipKeyPathsForPrefetching: to prefetch the ingredients relationship or execute a fetch request that fetch the set with the appropriate predicate).
If you need specific data only, then use the fetch request approach to retrieve only the data you need.
if you intend to repeatedly access the relationship for queries and info, just fetch the entire set by using prefetching, and query in-memory.
My point is:
Think of the approach that minimize your disk access (in any case you need at least 1 access).
If your data is too large to fit in memory, or to be queried in memory, perform a fetch to get only the data you need.
Now:
1.Consistency - Pick a method you find comfortable and stick with it (i use prefetching)
2.Readability - Using a property is much more readable then executing a query, however it is less efficient if not using prefetching.
3.Performance - Disk access degrade performance, but is unavoidable in some situations
4.Robustness - A fetch request show that you know what is best for your data usage. use it wisely.
To make sure you are minimising disk access, turn SQLite debug on
(-com.apple.CoreData.SQLDebug)
Edit:
Faulting behaviour
In this example it seems natural to use recipe.ingredients, but I
could equally use an NSFetchRequest for Ingredient entities with an
NSPredicate to match by recipe.
Why would you do the latter when you can do the former? You already have the recipe, and it already has a set of ingredients, so there's no need to look at all the ingredients and filter out just those that are related to the recipe that you already have.
Now let's say I want only the ingredients that are 'collected'. This
is less clear cut to me - should I use a fetch request for ingredients
with a predicate restricting by recipe and collected state? Or loop
through recipe.ingredients?
Apply the predicate to the recipe's ingredients:
NSPredicate *isCollected = [NSPredicate predicateWithFormat:#"collected == YES"];
NSSet *collectedIngredients = [recipe.ingredients filteredSetUsingPredicate:isCollected];
At the other end of the scale, perhaps I need only ingredients from
this recipe that also appear in other recipes. Now, the fetch request
seems more appealing.
Again, using a fetch request here seems wasteful because you already have easy access to the set of ingredients that could be in the final result, and that's potentially a much smaller set than the set of all ingredients. Use the same approach as above, but change the predicate to test the recipes associated with each ingredient. Something like:
NSPredicate *p = [NSPredicate predicateWithFormat:#"recipes > 1"];
NSSet *i = [recipe.ingredients filteredSetUsingPredicate:p];
What is the correct general approach?
Fetch requests are a good way to search through all instances of a given entity. You're always going to start with a fetch request to get some objects to work with. But when the objects you want are somehow related to an object that you already have you can (and should) use those relationships to get what you want.

SQL SELECT with table aliases in Core Data

I have the following SQL query that I want to do using Core Data:
SELECT t1.date, t1.amount + SUM(t2.amount) AS importantvalue
FROM specifictable AS t1, specifictable AS t2
WHERE t1.amount < 0 AND t2.amount < 0 AND t1.date IS NOT NULL AND t2.date IS NULL
GROUP BY t1.date, t1.amount;
Now, it looks like CoreData fetch requests can only fetch from a single entity. Is there a way to do this entire query in a single fetch request?
The best way I know is to crate an abstract parent entity for entities you wish to fetch together.
So if you have - 'Meat' 'Vegetables' and 'Fruits' entities, you can create a parent abstract entity for 'Food' and then fetch for all the sweet entities in the 'Food' entity.
This way you will get all the sweet 'Meat' 'Vegetables' and 'Fruits'.
Look here:
Entity Inheritance in Apple documentation.
Nikolay,
Core Data is not a SQL system. It has a more primitive query language. While this appears to be a deficit, it really isn't. It forces you to bring things into RAM and do your complex calculations there instead of in the DB. The NSSet/NSMutableSet operations are extremely fast and effective. This also results in a faster app. (This is particularly apparent on iOS where the flash is slow and, hence, big fetches are to be preferred.)
In answer to your question, yes, a fetch request operates on a single entity. No, you are not limited to data on that entity. One uses key paths to traverse relationships in the predicate language.
Shannoga's answer is one good way to solve your problem. But I don't know enough about what you are actually trying to accomplish with your data model to judge whether using entity inheritance is the right path for your app. It may not be.
Your SQL schema from a server may not make sense in a CD app. Both the query language and how the data is used in the UI probably force a different structure. (For example, using a fetched results controller on iOS can force you to denormalize your data differently than you would on a server.)
Entity inheritance, like inheritance in OOP, is a stiff technology. It is hard to change. Hence, I use it carefully. When I do use it, I gain performance in some fetches and some simplification in other calculations. At other times, it is the wrong answer, performance wise.
The real answer is a question: what are you really trying to do?
Andrew

Resources