NSSortdescriptor ineffective on fetch result from NSManagedContext - objective-c

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.

Related

Fetched Property in Core Data

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;

NSArray count returns correct number but faults with data access

I am attempting to get a count of items in an array to place it in the right detail of a cell. Interestingly enough I am getting the proper count but my when I try to NSLog the array it returns <fault>.
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:#"muscleGroup == %# && specificExercise == %# && date >= %# && date < %#", theMuscleGroup, theExercise, fromDate, toDate];
NSFetchRequest *fetchRequestTemp = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"WeightLiftingInfo" inManagedObjectContext:_managedObjectContext];
[fetchRequestTemp setPredicate:searchPredicate];
[fetchRequestTemp setEntity:entity];
NSError *error;
NSArray *tempArray = [[NSArray alloc] initWithArray:[_managedObjectContext executeFetchRequest:fetchRequestTemp error:&error]];
NSLog(#"%#", tempArray);
NSLog(#"%d", [tempArray count]);
My Question:
Basically, what am I doing wrong? Am i initing the array improperly or trying to log it improperly?
I have been researching for hours and trying different coding, but cannot figure it out. Any help would be appreciated. If you need more info or I am doing something against guidelines according to SO please let me know before you close this thread or "-1". HAVE MERCY, I am new and leaning:)
When you execute your fetch request, you get back a managed object fault. That is, a proxy-like object that represents the results even though the real data objects haven't been loaded from the data store yet. The fault knows how many objects are in the array, etc., and when you access the objects the fault will transparently load the real objects.
So, situation normal, don't worry about it. Try using the results instead of logging them and I think you'll find that things work fine.

Possible issue with fetchLimit and fetchOffset in a Core Data query

I have a very sporadic bug with a Core Data query containing fetchLimit and fetchOffset. Once in a long while (I've seen it happen once, as has another tester), the fetchOffset seems to be ignored. The query looks like this:
NSFetchRequest *fetch = [[NSFetchRequest alloc] initWithEntityName:#"MyEntity"];
NSSortDescriptor *dateDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:dateDescriptor];
[fetch setSortDescriptors:sortDescriptors];
fetch.fetchOffset = 500;
fetch.fetchLimit = 1;
NSError *error = nil;
NSArray *objects = [self.managedObjectContext executeFetchRequest:fetch error:&error];
if (objects.count) {
MyEntity *objectAtLimit = [objects objectAtIndex:0];
}
This almost always returns the 501st object as desired, but on those two occasions where it broke it returned the first object.
The query is never run unless there are >500 rows in the database. I'm using iOS5. The managedObjectContext has a mainQueueConcurrencyType.
It seems to be the same behavior as reported in this question: Paging results from Core Data requests, which was never resolved (or at least not on list.) In that case the fetchOffset appeared to be either ignored or respected based on the data model being tested against.
I'm probably going to rewrite the query without the fetchOffset, just in case that is the problem, since the performance shouldn't be an issue. But I'm wondering if anyone has thoughts about where the bug might be here.
Ran across a similar problem this morning and noticed if my NSManagedObjectContext has unsaved changes that the fetchOffset may be ignored for whatever reason. After saving the context the fetchOffset is interpreted correctly.
The problem indeed seems the be connected with unsaved changes in the NSManagedObjectContext. If you set the includesPendingChanges property to false the NSFetchRequest with limit + offset will work as expected.
fetchRequest.includesPendingChanges = false

Deadlock in fetch routine

i am stuck with my first GCD and first core-data using application =)
two views access the same data (which is handled by a single DAO).
if i wait for the current view to finish loading its content no problem occors when changing view.
however: if i change the view (its tabbased) while one controller tries to fetch data from my model, the new controller tries the same and the threads 'collide' and my application freezes.
the freeze occurs in this line of code of my DAO:
NSArray *results = [managedObjectContext executeFetchRequest:fetch error:&error];
reloadAllMonth() accesses the fetch routine of my DAO
how i load the data in the first controller:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[self reloadAllMonth];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.allMonthTable reloadData];
});
in the second viewcontroller the first thing i do is update my DAO, this of course uses (beneath others) the very same fetch routine i called before:
[self.dataHandler updateData];
i have tried two approaches so far:
first using a c-semaphore:
-(NSArray *)fetchAllMonthExpenses{
//#return: array of all expenses in month (day & month type)
NSNumber *monthNumber = [self getMonthNumber:[NSDate date]];
NSEntityDescription *exp = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
[fetch setEntity:exp];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"month == %#",monthNumber]];
NSError *error = nil;
sem_wait(&isLoading);
NSArray *results = [self.managedObjectContext executeFetchRequest:fetch error:&error];
sem_post(&isLoading);
return results;
}
second using the synchronized directive
-(NSArray *)fetchAllMonthExpenses{
//#return: array of all expenses in month (day & month type)
NSNumber *monthNumber = [self getMonthNumber:[NSDate date]];
NSEntityDescription *exp = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetch = [[NSFetchRequest alloc]init];
[fetch setEntity:exp];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"month == %#",monthNumber]];
NSError *error = nil;
#synchronized(self.class){
NSArray *results = [self.managedObjectContext executeFetchRequest:fetch error:&error];
return results;
}
}
sadly both of the approaches did not work, the application freezes whatever i do.
so my question is: what am i doing wrong (as i mentioned first time using threads), what am i missing, where should i look?
this has been keeping me busy for 2 days now and i cant seem to wrap my head around it :/
An NSManagedObjectContext and all of the NSManagedObjects inside it are not thread safe.
Whatever thread you use to create the context, that needs to be the only thread where you do anything relating to that context. Even just reading values from one of the managed object must be done on that thread and not on any other thread.
If you need two threads which both deal with the same database, you've got two options:
use dispatch_sync() to jump into the other thread momentarily to perform all read/write operations on the managed objects and/or the context
Or:
create a second NSManagedObjectContext in the other thread for the same database, and keep any changes made to the two contexts in sync.
The first option is much easier, but may remove much of the benefits of threading. The second option is harder, but it can be done, and there is a fairly good API for keeping two contexts on different threads in sync.
Lookup the Core Data Programming Guide for more details.

NSManagedObjectContext's registeredObjects won't return anything unless a fetch is executed beforehand

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.