Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSSortdescriptor ineffective on fetch result from NSManagedContext

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.

like image 319
poof86 Avatar asked Apr 20 '12 21:04

poof86


1 Answers

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.

like image 63
Jacob Gorban Avatar answered Oct 16 '22 23:10

Jacob Gorban