I have a large number of objects in Core Data. Does the following only load what is needed for the UI as and when is needed or does it load all objects up front?
NSFetchedResultsController does have a fetchedObjects property.. does it means it fetches everything upfront? What is the right way to fix this?
NSManagedObjectContext *context = # get from somewhere
NSManagedObjectModel *model = context.persistentStoreCoordinator.managedObjectModel;
NSDictionary *vars = #{...};
NSFetchRequest *fetchRequest = [model fetchRequestFromTemplateWithName:#"..."
substitutionVariables:vars];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"..." ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
fetchRequest.sortDescriptors = sortDescriptors;
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:#".."];
You generally shouldn't use fetchedObjects. It will always be the full list of fetched objects, some of which may be in memory and some may be faults.
The whole point is that you want the FRC to load data (to fault objects) only as required. But, for it to do that, you need to tell it how much to load based on what your UI can display at any one time (the maximum number of items that could be on screen at the same time).
To do that you need to set the fetchBatchSize on the NSFetchRequest. Once you've done that the FRC will load a new 'page' of results (into memory) as required (when your UI is scrolled and new requests are made to the FRC for data).
Technically, it isn't the FRC that's doing this, it's the array object returned by the fetch which initially contains 'empty' objects and which transparently faults batches of objects on demand.
Related
My Core Data model has a Note entity with a to-many relationship to a Link entity, accessed through a links property on Note.
I'm trying to fetch the count of the links property for each note in Core Data. To do so, I followed the example at the link below, but I'm getting an unexpected result.
Chained expressions to perform calculations in Core Data
Here's how I'm configuring the fetch request and related expression descriptions:
// Create the expression description that should return the link count for each note.
NSExpression *linksExpression = [NSExpression expressionForKeyPath:#"links"];
NSExpression *countExpression = [NSExpression expressionForFunction:#"count:" arguments:#[linksExpression]];
NSExpressionDescription *linkCountExpressionDescription = [[NSExpressionDescription alloc] init];
linkCountExpressionDescription.name = #"linkCount";
linkCountExpressionDescription.expression = countExpression;
linkCountExpressionDescription.expressionResultType = NSInteger32AttributeType;
// Execute a fetch request that should return an array of dictionaries, each with a single "linkCount" key.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Note"];
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = #[linkCountExpressionDescription];
NSArray *countsArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
The SQLite store I'm working with contains 1000 notes, so I expect to get an array of 1000 dictionaries, each containing a LinkCount key and the count of its links relationship. Instead, I get a single dictionary that gives me the total count of all links.
What should I do differently to get the link counts for each note?
I think I'm just missing something obvious here, but it's one of those frustrating things that's somehow eluding me.
I have a Core Data Entity called ProjectEntry. The ProjectEntry objects are displayed in uitableviews, using various attributes, arranged by date (attributes include things like "dateAsNSDate"[NSDate], "month"[NSString], "year"[NSString], "dayOfWeek"[NSString]).
I'm using an NSFetchedResultsController to populate the table views.
When I initially create and save the ProjectEntry object, the "dateAsNSDate" attribute is parsed and converted into various NSStrings. One string, also an attribute, is called "concatMonthAndYear". It takes the "month" and "year" strings and just joins them. So I get things such as "January 2014", "February 2015", etc.
I use the "concatMonthAndYear" as my cell.textLabel.text string to display in my tableview cells.
I use the NSDate attribute to sort the tableview rows (sortDescriptor), and the "year" attribute as my section headers (sectionNameKeyPath).
So right now, I'd have a tableview section called "2014", with tableview rows each representing a Core Data object, named things like "January 2014", February 2014", etc, in said section.
I can tap on one of those rows, segue to another tableview, and list all objects created in January 2014, for example, by using an NSPredicate on the second tableview.
However, on my first tableview, each Core Data object created is represented by its own tableview row. So I'll get multiple rows reading "January 2014" or "May 2015" or whatever. They're valid saved objects, and I want them, but I'd like to prevent a new row from being created if that "concatMonthAndYear" already exists. If a row titled "January 2014" already exists, I don't want a new row created. I want the new Core Data object stored, just not a new tableviewrow representing it. I only need one row with "January 2014", for example, to segue into a table listing ALL the entities from January 2014.
I know how to use an NSPredicate to get ALL the January 2014 objects into the second table, but how do I get JUST ONE object into the first table?
Is NSPredicate not the right device for that? Should I be somehow preventing a new cell from being created in the UITableView delegate methods? Each tableview row should be unique, and I'm stuck on whether it should be handled with the NSFetchedResults controller or in the tableview delegate methods?
Or some other way?
Can someone point in the right direction?
EDITED TO INCLUDE CODE:
- (void)setupFetchedResultsController
{
// 1 - Decide which Entity
NSString *entityName = #"ProjectEntry";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
[request setReturnsDistinctResults:YES];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:#[#"monthYearTableSecHeader", #"year"]];
// 3 - Filter it
//request.predicate = [NSPredicate predicateWithFormat:#" "];
// 4 - Sort it
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"year"
ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:#"dateAsNSDate"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)], nil];
//5 - Fetch
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"year"
cacheName:nil];
[self performFetch];
}
You could use
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:#[#"concatMonthAndYear", #"year"]];
This will cause the fetch request to return distinct dictionary objects corresponding to "January 2014", etc. objects.
However, you cannot use a fetch request controller's delegate methods (to hear of updates to the data).
If you need to hear updates, I suggest you add a layer of indirection to your data, where MonthEntry is an object representing yearly months and have a one to many relationship with ProjectEntry, which is your normal entity. This way, you can set the fetch request entity to MonthEntry.
i've a problem with my table!
i use a parsing tableview but when i change view, my table loses data. So i decide to save all data to nsuserdefault; but, here the problem, NSUserDefault warns me:
"Note that dictionaries and arrays in property lists must also contain only property values."
NB: itemsToDisplay is a NSMutableArray and contain title, url, data and summary of parsedItems.
Well, here my code:
self.itemsToDisplay = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"items"] mutableCopy];
if (!self.itemsToDisplay) {
self.itemsToDisplay = [[NSMutableArray alloc] init];
}
self.itemsToDisplay = [[NSMutableArray alloc]init];
self.itemsToDisplay = [parsedItems sortedArrayUsingDescriptors:
[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"date"
ascending:NO] autorelease]]];
[[NSUserDefaults standardUserDefaults] setObject:self.itemsToDisplay forKey:#"items"];
[[NSUserDefaults standardUserDefaults] synchronize];
I suppose the problem is setObject:self.itemsToDisplay, but i don't know how solve it.
Thank You guys..
First lets mention that the table cannot lose data because it does not hold any user data. The data is either provided through bindings or through delegation see NSTableViewDataSource in Apples documentation).
Second, the first three assignments to self.itemsToDisplay serve no purpose (unless you have side-effects in the setter) because they are all overridden by the last assignment.
Finally, if this code is already in the delegate then the delegate should be instantiated in your NIB file for the data to survive past a view swap. If your delegate is an object that is instantiated with your view it will also die with it along with all of the data and writing to the user-defaults is a bad idea for what you are trying to achieve. Simply set the delegate to an object whose lifetime is greater than that of both views.
self.itemsToDisplay = [parsedItems sortedArrayUsingDescriptors:
[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"date"
ascending:NO] autorelease]]];
//First lets encode it
NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:self.itemsToDisplay];
[userDefault setObject:myEncodedObject forKey:[NSString stringWithFormat:#"sample"]];
I'm working on a recipe book right now using Core Data. It's the first time I'm using CoreData and it's working so far, but I'm having some trouble using it in the iPad's split view.
Here's my object model:
http://i621.photobucket.com/albums/tt295/Naosu_FFXI/ObjectModel.png
In my app, steps and ingredients are shown in two tables in the detail view. When I have one recipe, it works as expected. However, the NSFetchedResultsControllers for both tables pulls all the information regardless of what recipe you select. So using an NSPredicate seems to be the most obvious choice.
Here is my code snippet for the NSPredicate:
filteredIngredientsArray = [[NSMutableArray alloc] init];
.... snip ....
//---------- Filtering the ingredients by name ----------
NSError *error = nil;
NSPredicate *ingredientsPredicate = [NSPredicate predicateWithFormat:#"recipe.recipeName == '%#'", selectedName];
[fetchRequest setPredicate:ingredientsPredicate];
NSLog(#"Filtering the INGREDIENTS with this: %#", selectedName);
NSArray *loadedIngredients = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
filteredIngredientsArray = [[NSMutableArray alloc] initWithArray:loadedIngredients];
[self.ingredientTable reloadData];
When I use this, my tables don't get filled period.... so it's definitively working, but not working as I want it to. My NSLog shows that the value of the selectedName variable is the name of the recipe the user taps on.
This has been driving me up the wall and it really bothers me, so any feedback/help would be appreciated.
Remove the single quotes around %# in the predicate format:
[NSPredicate predicateWithFormat:#"recipe.recipeName == %#", selectedName]
I am trying to parse a lot of text files and organize their contents as managed objects. There are a lot of duplicates in the text files, so one of the "collateral" tasks is to get rid of them.
What i am trying to do in this respect is to check whether an entity with the given content exists, and if it doesn't, i create one. However, i have different entities with different attributes and relationships. What i want is a kind of function that would take a number of attributes as an input and return a new NSManagedObject instance, and i wouldn't have to worry if it was inserted into the data store or fetched from it.
Is there one?
I must also say that i am a noob at core data.
Some more detail, if you want:
I am trying to write a sort of dictionary. I have words (Word{NSString *word, <<-> Rule rule}), rules (Rule{NSString name, <->>Word word, <<->PartOfSpeech partOfSpeech, <<-> Ending endings}), parts of speech (PartOfSpeech{NSString name, <<-> Rule rule}) (i hope the notation is clear).
Two words are equal, if they have the same word property, and "linked" to the same rule. Two rules are the same, if they have the same endings and part of speech.
So far i've written a method that takes NSPredicate, NSManagedObjectContext and NSEntityDescription as an input, and first queries the datastore and returns an entity if it finds one, or creates a new one, inserts it into the datastore and returns it. However, in this case I cannot populate the new entity with the necessary data (within that method), so i have to either pass an NSDictionary with the names of attributes and their values and insert them, or return by reference a flag as to whether i created a new object or returned an old one, so that i could populate it with the data outside.
But it looks kind of ugly. I'm sure there must be something more elegant than that, i just couldn't find it. Please, help me if you can.
Your basically on the right path. Core Data is an object graph. There not a lot of dynamic built in. There's also no "upsert". like you surmise, you have to fetch and if it doesn't exist, you insert one.
Here is what I have just started using to handle a fetch-or-create scenario. I am using a top level managed object which contains a few to-many relationships to subordinate objects. I have a class that houses a few arrays of data (those are not shown here). This class is responsible for saving and retrieving to and from core data. When the class is created, I do a fetch-or-create to access my top level NSManagedObject.
#implementation MyDataManagerClass
...
#synthesize MyRootDataMO;
- (MyDataManagerClass *) init {
// Init managed object
NSManagedObjectContext *managedObjectContext = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// Fetch or Create root user data managed object
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
if (result == nil) {
NSLog(#"fetch result = nil");
// Handle the error here
} else {
if([result count] > 0) {
NSLog(#"fetch saved MO");
MyRootDataMO = (MyRootDataMO *)[result objectAtIndex:0];
} else {
NSLog(#"create new MO");
MyRootDataMO = (MyRootDataMO *)[NSEntityDescription insertNewObjectForEntityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
}
}
return self;
}
...