In my core data model, a Person has one or more Cars, specified by the unordered to-many relationship 'Cars'. Frequently, I need to retrieve a Person's cars ordered by datePurchased, or by dateLastUsed.
Until now, I have been adding my own method to Person for carsByDatePurchased. This uses a sort descriptor to sort the NSSet cars and return an NSArray.
Could/should I instead use a Fetched Property for this? I am experiencing some performance overhead using the sort descriptor every time I need the cars in a certain order, even going so far as implementing my own caching of carsByDatePurchased. It looks like the fetched property is cached for me - is that correct?
What are the limitations of a fetched property vs my own implementation?
And crucially, does the fetched property's value persist between executions? If I update the fetched property and save my context, is the value stored for the next time I launch the application?
A fetched property will work, and indeed I used it in my own project with a Post->Comment relationship which needs to be sorted by 'date added index'.
There are a number of caveats: You cannot specify a sort descriptor in the visual editor and have to specify it in code.
I use something like this
// Find the fetched properties, and make them sorted...
for (NSEntityDescription *entity in [_managedObjectModel entities])
{
for (NSPropertyDescription *property in [entity properties])
{
if ([property isKindOfClass:[NSFetchedPropertyDescription class]])
{
NSFetchedPropertyDescription *fetchedProperty = (NSFetchedPropertyDescription *)property;
NSFetchRequest *fetchRequest = [fetchedProperty fetchRequest];
// Only sort by name if the destination entity actually has a "index" field
if ([[[[fetchRequest entity] propertiesByName] allKeys] containsObject:#"index"])
{
NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:#"index"
ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
}
}
}
}
In My Post entity I have a fetched property called "sortedComments" which is defined as:
post == $FETCH_SOURCE
where posts have a to-many "comments" relationship and comments have a "post" inverse
In opposition to the other answers here: The benefits of using a fetched property like this, is CoreData takes care of the caching and invalidating the cache as comments for a post or indeed the post that owns them changes.
If you want to gain some performance, do your fetch with an NSFetchedResultsController and have it working with a cache. Next time you perform the same fetch, the fetch will be faster. In your particular name, you will have to cache names. Take a look at the NSFetchedResultsController documentation.
A fetched property is basically a fetch request. I am not aware of ways to add sort descriptors to these properties in the GUI, but I may be wrong. But why not just create a fetch request in your carsByDatePurchased method and provide a sort descriptor? It returns an array or the results (which you can wrap cheaply in an NSOrderedSet with copyItems: flag set to no).
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"DataRecord" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *obj in fetchedObjects) {
NSLog(#"Name: %#", [obj valueForKey:#"name"]);
NSLog(#"Info: %#", [obj valueForKey:#"info"]);
NSLog(#"Number: %#", [obj valueForKey:#"number"]);
NSLog(#"Create Date: %#", [obj valueForKey:#"createDate"]);
NSLog(#"Last Update: %#", [obj valueForKey:#"updateDate"]);
}
NSManagedObject *obj = [fetchedObjects objectAtIndex:0];
[self displayManagedObject:obj];
selectedObject = obj;
Related
I am very new to the Core Data programming. I have a question to which I hope to get some clarification.
Supposing I have an NSManagedObject called Company, with the following attributes:
companyName
companyEmail
companyPhoneNo
companyUserName
companyPassword
In this object the companyName attribute is indexed.
So, my question is, how can I make sure that there will be only entry with same companyName, companyEmail, companyPhoneNo, companyUserName and companyPassword?
Do I need to make a request to check if there are any records with the same attribute values or is a simple check with the object id sufficient?
Thanks.
Here's an example may be helps:
NSError * error;
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:NSStringFromClass([self class])
inManagedObjectContext:managedObjectContext]];
[fetchRequest setFetchLimit:1];
// check whether the entity exists or not
// set predicate as you want, here just use |companyName| as an example
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"companyName == %#", companyName]];
// if get a entity, that means exists, so fetch it.
if ([managedObjectContext countForFetchRequest:fetchRequest error:&error])
entity = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
// if not exists, just insert a new entity
else entity = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([self class])
inManagedObjectContext:managedObjectContext];
[fetchRequest release];
// No matter it is new or not, just update data for |entity|
entity.companyName = companyName;
// ...
// save
if (! [managedObjectContext save:&error])
NSLog(#"Couldn't save data to %#", NSStringFromClass([self class]));
Tip: countForFetchRequest:error: does not fetch entity actually, it just returns a number of entities that match the predicate you set before.
You have two options for maintaining you store with no duplicates:
Make fetch in insert.
Insert all the new data and then delete the duplicates before saving.
What is faster and more convenient? Presumably first way. But you'd better test it using Instruments and find the right way for your app.
Here are the docs on this question.
http://developer.apple.com/library/mac/ipad/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174-SW1
I'm trying to sort my NSFetchRequest result using a NSSortdescriptor using a key pointing to a NSDate value. My fetch results come out totally random for no clear reason.
The NSManagedObjectContext I'm using is updated with a save from a nested child context created on a subclass of NSOperation. I know all this is done successfully because I can get all the data needed from the parent (main) context. Fetching from it just wont sort on date!
Strange thing is; predicates for selecting the entities (called "Tweet") between two dates works just fine!
Here's some code to illustrate my problem:
NSSortDescriptor* timeDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"time"
ascending:NO
selector:#selector(compare:)];
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"Tweet"];
[request setSortDescriptors:[NSArray arrayWithObjects:timeDescriptor, nil]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"((time >= %#) AND (time <= %#))",startDate,endDate];
[request setPredicate:predicate];
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[[NSApp delegate] managedObjectContext]];
[context performBlock:^{
NSError* error = nil;
NSArray* results = nil;
results = [context executeFetchRequest:request error:&error];
// Results here are not ordered correctly
// 2nd try sorting results using fetched array (works!)
results = [results sortedArrayUsingDescriptors:[NSArray arrayWithObjects:timeDescriptor, nil]];
// This works too but not needed anymore
/*results = [results sortedArrayUsingComparator:^(id obj1, id obj2) {
Tweet* tweet1 = (Tweet*)obj1;
Tweet* tweet2 = (Tweet*)obj2;
//return [tweet1.time compare:tweet2.time]; // ascending
return [tweet2.time compare:tweet1.time]; // descending
}];*/
if ([results count] > 0) {
for (uint i = 0; i < [results count]; i++) {
Tweet* tweet = [results objectAtIndex:i];
NSDate* date = Tweet.time;
NSLog(#"tweet date: %#", date);
}
}
}];
Can anybody tell me why the NSSortDescriptor isn't working for my fetches?
Thanks!
-- Update --
It seems the NSSortDescriptor works fine when I fetch from the main (parent) managedObjectContext on the main thread without using the performBlock method. This still doesn't help me do sorted fetches on a NSPrivateQueueConcurrencyType managedObjectContext. Creating the NSFetchRequest, NSSortDescriptor and NSPredicate inside the performBlock doesn't fix the problem either.
I hit the problem as well.
I've found out that unless the data is saved all the way back to Persistent Store, the sorting won't work if the data in the master context is dirty, i.e. modified.
For example, if the contexts are clean, without pending changes, the sorting works.
If I only change one attribute of an entity in the parent context, then the sorting in the private queue child context doesn't work. That's very unfortunate. I also do sorting with array method now but it's not that fast as sorting in the NSFetchRequest, especially since my data is already indexed by that key. It would've been much faster to sort it in the fetch request.
My guess is that since there are unsaved changes in the context and NSFetchRequest goes to the SQLite database itself, where the changes do not yet exist (context not saved), it can't sort on the database level at all.
But overall, it's very confusing and smells like a bug.
I had exactly the same issue. I have solved the problem by setting the includesPendingChanges property in the NSFetchRequest instance to NO.
When using the default compare: selector, you can simplify the descriptor:
NSSortDescriptor* timeDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"time"
ascending:NO];
But that's an aside. I think the key is the fact that you're updating from a nested child context. Validate that your objects have permanent object ids; they might not have received them yet, and thus that might be the issue with your fetch. If it is, then try calling objectPermanentIDsForObjects: prior to saving the nested child context.
Cheers,
I'm experiencing a problem with core data, I guess I'm just looking in the wrong direction again.
My managedObjectContext will return an empty NSSet if I call registeredObjects on it. If I execute a fetch beforehand however, it will return the same objects that as the fetch did just a moment ago.
There's no multithreading going on.
Here's what I do:
[self setupContext]; // This will set up managedObjectContext, which is a property of this class
// Fetching...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *foo = [NSEntityDescription entityForName:#"Foo" inManagedObjectContext:managedObjectContext];
[request setEntity:foo];
NSError *fetchError = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&ftchError];
NSLog(#"Fetch returned %i objects.", [fetchResults count]);
[request release];
// Done fetching...
NSArray *allObjects = [[managedObjectContext registeredObjects] allObjects];
NSLog(#"Context contains %i objects...", [allObjects count]);
The store contains 30 objects. If I run the code above, both NSLogs will report five objects. If I remove the fetch part between the two comments, it will report zero objects for the whole context.
Note that I am at no point commiting or otherwise changing the contexts contents.
Do I need to force the context into refreshing itself first? I've never done this before though and I don't recall registeredObjects failing on me like this on other occasions in the first place.
Any suggestions appreciated!
Toastor
You may be confused about what registeredObjects means. This is the set of objects that are currently in the NSManagedObjectContext. This is not the set of objects in the store, just the ones in the context. If you haven't fetched or otherwise registered the objects in the context, then they won't be in registeredObjects.
I'm having some trouble with inserts using a NSFetchedResultsController with a simple to-one relationship. When I create a new Source object, which has a to-one relationship to a Target object, it seems to call - [(void)controller:(NSFetchedResultsController *)controller didChangeObject ... ] twice, with both NSFetchedResultsChangeInsert and NSFetchedResultsChangeUpdate types, which causes the tableview to display inaccurate data right after the update.
I can recreate this with a simple example based off the standard template project that XCode generates in a navigation-based CoreData app. The template creates a Event entity with a timeStamp attribute. I want to add a new entity "Tag" to this event which is just a 1-to-1 relation with Entity, the idea being that each Event has a particular Tag from some list of tags. I create the relationship from Event to Tag in the Core Data editor, and an inverse relationship from Tag to Event. I then generate the NSManagedObject sub-classes for both Event and Tag, which are pretty standard:
#interface Event : NSManagedObject {
#private
}
#property (nonatomic, retain) NSDate * timeStamp;
#property (nonatomic, retain) Tag * tag;
and
#interface Tag : NSManagedObject {
#private
}
#property (nonatomic, retain) NSString * tagName;
#property (nonatomic, retain) NSManagedObject * event;
I then pre-filled the Tags entity with some data at launch, so that we can pick from a Tag when inserting a new Event. In AppDelegate, call this before returning persistentStoreCoordinator:
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Tag" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
//check if Tags haven't already been created. If not, then create them
if (fetchedObjects.count == 0) {
NSLog(#"create new objects for Tag");
Tag *newManagedObject1 = [NSEntityDescription insertNewObjectForEntityForName:#"Tag" inManagedObjectContext:context];
newManagedObject1.tagName = #"Home";
Tag *newManagedObject2 = [NSEntityDescription insertNewObjectForEntityForName:#"Tag" inManagedObjectContext:context];
newManagedObject2.tagName = #"Office";
Tag *newManagedObject3 = [NSEntityDescription insertNewObjectForEntityForName:#"Tag" inManagedObjectContext:context];
newManagedObject3.tagName = #"Shop";
}
[fetchRequest release];
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Now, I changed the insertNewObject code to add a Tag to the Event attribute we're inserting. I just pick the first one from the list of fetchedObjects for this example:
- (void)insertNewObject
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Event *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityTag = [NSEntityDescription entityForName:#"Tag" inManagedObjectContext:context];
[fetchRequest setEntity:entityTag];
NSError *errorTag = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&errorTag];
if (fetchedObjects.count > 0) {
Tag *newtag = [fetchedObjects objectAtIndex:0];
newManagedObject.tag = newtag;
}
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
I want to now see the tableview reflecting these changes, so I made the UITableViewCell to type UITableViewCellStyleSubtitle and changed configureCell to show me the tagName in the detail text label:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Event *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [[managedObject valueForKey:#"timeStamp"] description];
cell.detailTextLabel.text = managedObject.tag.tagName;
}
Now everything's in place. When I call insertNewObject, it seems to create the first row fine, but the 2nd row is a duplicate of the first, even though the timestamp should be a few seconds apart:
When I scroll the screen up and down, it refreshes the rows and then displays the right results with the correct time. When I step through the code, the core problem comes up: inserting a new row seems to be calling [(NSFetchedResultsController *)controller didChangeObject ...] twice, once for the insert and once for an update. I'm not sure WHY the update is called though. And here's the clincher: if I remove the inverse relationship between Event and Tag, the inserts starts working just fine! Only the insert is called, the row isn't duplicated, and things work well.
So what is it with the inverse relationship that is causing NSFetchedResultsController delegate methods to be called twice? And should I just live without them in this case? I know that XCode gives a warning if the inverse isn't specified, and it seems like a bad idea. Am I doing something wrong here? Is this some known issue with a known work-around?
Thanks.
With regards to didChangeObject being called multiple times, I found one reason why this will going to happen. If you have multiple NSFetchedResultsController in your controller that shares NSManagedObjectContext, the didChangeObject will be called multiple times when something changes with the data. I stumbled on this same issue and after a series of testing, this was the behavior I noticed. I have not tested though if this behavior will going to happen if the NSFetchedResultsControllers does not share NSManagedObjectContext. Unfortunately, the didChangeObject does not tell which NSFetchedResultsController triggered the update. To achieve my goal, I ended up using a flag in my code.
Hope this helps!
You can use [tableView reloadRowsAtIndexPaths:withRowAnimation:] for NSFetchedResultsChangeUpdate instead of configureCell method.
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
I had the same problem. And have a solution.
Under certain circumstances, the NSFetchedResultsController gets fired twice when calling the -(BOOL)save: on the managed object context, directly after inserting or manipulating.
In my case, I'm doing some magic with the object in the NSManagedObject -(void)willSave method, which causes the NSFetchedResultsController to fire twice. This seems to be a bug.
Not to manipulate inserted objects while being saved did the trick for me!
To delay the context save to a later run loop seems to be another solution, for example:
dispatch_async(dispatch_get_main_queue(), ^{ [context save:nil]; });
Objects in NSFetchedResultsController must be inserted with permanent objectID. After creating object and before saving to persistent store, it has temporary objectID. After saving object receive permanent objectID. If object with temporary objectID is inserted into NSFetchedResultsController, then after save object and change its objectID to permanent, NSFetchedResults controller may report about inserting fake duplicate object.
Solution after instantiating object that will be fetched in NSFetchedResultsController - just call obtainPermanentIDsForObjects on its managedObjectContext with it.
I have a UITableView that displays a subset of a large number of entities named "Documents". The subset is defined by another entity "Selection". Selections are named, ordered list of documents.
It Works fine, except when I want to change the displayed selection at run time. I get only a blank list.
Basically, I need to change the predicate that my NSFetchedResultsController holds so that the new predicate uses the another Selection. I couldn't make it work. My last attempt is to get rid of the NSFetchedResultsController altogether and reallocate it:
- (void) displaySelection:(Selection *)aSet
{
self.currentSelection = aSet;
self.fetchedResultsController = nil;
// methods here don't all use the property but directly the ivar, so we must trigger the getter
[self fetchedResultsController];
[self.tableView reloadData];
}
And of course, the NSFetchedResultsController getter does the right thing:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) { return fetchedResultsController; }
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setPredicate:predicate];
<snip>
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
<snip>
return fetchedResultsController;
}
This code works the first time, because the Initial Selection is set. When displaySelection: is called, though, the tableview becomes blank.
A very similar question was asked at NSFetchedResultsController fetch request - updating predicate and UITableView
And the answer was to get rid of the NSFetchedResultsController. I don't want to do that, because NSFetchedResultsController brings a lot of useful goodies here (eg caching, partial loading...). The question still stands: how to "switch" data in a UITableView backed by a NSFetchedResultsController, where "switch" means having a different predicate, or even (not in my case) a different entity.
Note for the sake of completeness, that since the many-to-many relationship from Selection to Document is ordered, it is handled through an in-between lightweight entity called DocInSelection, which has an "ordering" property and two many-to-one relationships to Document and Selection.
Thanks for any suggestion.
Since NSFetchedResultsController(FRC) is an object, you can store instances of it like any other object.
One useful technique is to initialize and store several FRC in a dictionary and then set the tableview controller's fetchedResultController attribute to the FRC you need at the moment. This is useful for situations such as having a segmented control to sort on different attributes or entities in the same table. This technique has the advantage of maintaining the individual FRC caches which can speed fetches up significantly.
Just make sure to send the tableview itself a beginUpdates before you swap controllers and then an endUpdates when you are done. This prevents the table from asking for data in the narrow window when the FRC are being swapped out. Then call reloadData.
After I posted my question, I tried a variant of the code the OP of the other question showed. It works for me. Here it is:
- (void) displaySelection:(Selection *)aSet
{
if (aSet != self.currentSelection) {
self.currentSelection = aSet;
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSPredicate *predicate = nil;
NSEntityDescription *entity = nil;
entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.tableView reloadData];
}
While this may work there's a note in the iOS Reference Library that troubles me:
Important: You must not modify the
fetch request. For example, you must
not change its predicate or the sort
orderings.
Source: NSFetchedResultsController Class Reference
This additional note doesn't exist in the iOS 3.2 Reference Library.
Just wanted to point this out.
An important note: if you "overwrite" a fetchController object make sure you clear its .delegate first - otherwise you'll get crashes when deleting rows, etc as the old fetchController and its delegate get events.