NSSortdescriptor ineffective on fetch result from NSManagedContext
Asked Answered
B

3

5

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.

Bonnee answered 20/4, 2012 at 21:31 Comment(0)
C
5

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.

Creaky answered 14/5, 2012 at 8:1 Comment(0)
E
3

I had exactly the same issue. I have solved the problem by setting the includesPendingChanges property in the NSFetchRequest instance to NO.

Excerpta answered 28/5, 2012 at 23:50 Comment(0)
I
-1

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.

Infirmity answered 22/4, 2012 at 8:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.