0

I have a fetch request "foo" on which I have a sort descriptor (it sorts an Integer 16 attribute in descending order). I execute the fetch request thusly:

__block NSArray *results = nil;

[_managedObjectContext performBlockAndWait:
 ^{
     results = [_managedObjectContext executeFetchRequest:<foo> error:nil];
 }];

return results;

When I execute this fetch request under iOS 6+, the results are sorted as specified/expected (reverse integer index order).

However, when I execute it under iOS 5.1, the results are unsorted!

But if I then immediately apply the exact same sort descriptor to the results array thusly:

results = [results sortedArrayUsingDescriptors:<foo>.sortDescriptors];

the results are then sorted correctly.

Has anyone else encountered anything like this? If so, did you discover the cause?

Thanks!

Carl

P.S.: In case it proves relevant, here is the definition of "foo":

-(NSFetchRequest *)fetchRequestForUnparsedStoriesOfStoryListWithId:(id)storyListId
{
    NSPredicate *hasStoryListWithIdPredicate = [NSPredicate predicateWithFormat:@"ANY lists.id = %@", storyListId];
    NSPredicate *isUnparsedStoryOfStoryListWithIdPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[hasStoryListWithIdPredicate, self.isUnparsedPredicate]];

    NSFetchRequest *fetchRequestForUnparsedStoriesOfStoryListWithId = [NSFetchRequest fetchRequestWithEntityName:@"Story"];
    fetchRequestForUnparsedStoriesOfStoryListWithId.predicate = isUnparsedStoryOfStoryListWithIdPredicate;
    fetchRequestForUnparsedStoriesOfStoryListWithId.sortDescriptors = [NSArray arrayWithObject:self.descendingIndexSortDescriptor];

    return fetchRequestForUnparsedStoriesOfStoryListWithId;
}

and here is the definition of the sort descriptor:

-(NSSortDescriptor *)descendingIndexSortDescriptor
{
    if (! _descendingIndexSortDescriptor)
    {
        self.descendingIndexSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"index" ascending:NO];
    }

    return _descendingIndexSortDescriptor;
}

P.P.S.: A little more context on, well, the context of this fetch: This is occurring on app launch, after 1) a background-thread/background-MOC (a nested MOC with NSPrivateQueueConcurrencyType) parse of server information has determined some basic properties, then 2) created a set of Story entities in the background MOC having (inter alia) the index being sorted upon, and then 3) saved the changes on that background MOC. Only, and immediately, after those operations complete is the above fetch request performed, and it is performed on the same background thread and background MOC. Having read here: NSSortdescriptor ineffective on fetch result from NSManagedContext — 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", I tried forcing the save in step 3) all the way "up" the nested MOC chain to the persistent store (i.e., to disk), but that too had no effect: under iOS 5.1 (only) the results of this fetch are not sorted, unless I sort the results "manually" after performing the fetch.

Community
  • 1
  • 1
  • what are the descriptors. the CALL itself looks ok, so it must be due to the descriptors used – Daij-Djan Sep 27 '13 at 00:28
  • You might think so, but like I said, this works fine when I run under iOS 6+, but fails under iOS 5.1. Exactly the same code, exactly the same NSSortDescriptor construction: '[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:NO];' – Carl F. Hostetter Sep 27 '13 at 00:33
  • yes, I got that ;) but depending on the sort descriptor it could be only be supported by new CoreData 6 and not 5 – Daij-Djan Sep 27 '13 at 06:27

2 Answers2

1

OK, after further experimentation, I've determined that indeed, in iOS 5.1 (but not in iOS 6+), as stated here: NSSortdescriptor ineffective on fetch result from NSManagedContext — "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". If I save all entity changes (insertions, modifications, deletions) to the persistent store (performing synchronous saves all the way up the nested MOC chain to the disk-MOC) before executing the fetch request, the results are properly sorted. If I don't then the results are not sorted.

So: if your app needs to run under iOS 5.1, and if you use nested MOCs, you must either 1) save all the way "up" to your persistent store (e.g. disk) before executing a sorted fetch request, or 2) apply the sort yourself on the array of fetched results.

(Even though this problem is in fact answered in the linked post, I'm going to leave this question and answer up here, until/unless requested to remove it, because I think it is clearer and more explicit as to exactly what the problem and its solution is.)

Community
  • 1
  • 1
0
  1. You probably don't want to use performBlockAndWait this way. It's only intended for reentrancy, and as such it can't do some of the things that performBlock does for you - like autorelease pools and user events. User events are EXTREMELY important for core data to function as you expect. For example, changes made inside performBlockAndWait will not trigger NSManagedObjectContext notifications! See the iOS 5 Core Data release notes for more information.

  2. There are a number of known issues with CoreData on iOS 5.x that are fixed on iOS 6. Too many to list. There was one where the sorting would not work if there were changes in the parent context, and I do think there was another regarding sorting. Unfortunately I no longer have the radar numbers for these.

Do you get the same sorting issue if you use performBlock with a semaphore in place of performBlockAndWait, as illustrated in WWDC 2012 session 214 "Core Data Best Practices"?

quellish
  • 21,123
  • 4
  • 76
  • 83
  • "You probably don't want to use performBlockAndWait this way." — But how else am I supposed to execute fetch requests and return their results in a thread-safe manner? And why would this work under iOS 6+, but not under iOS 5.1? – Carl F. Hostetter Sep 27 '13 at 02:11
  • "User events are EXTREMELY important for core data to function as you expect." Understood (I think!). But there are no user events occurring at this point. I thought there might be a race-condition with context saving causing this behavior, but even forcing the persistent-store save to occur before this fetch request is executed had no effect. And again, it works fine under iOS 6+, but fails under iOS 5.1. Why should this be?! – Carl F. Hostetter Sep 27 '13 at 02:14
  • "WWDC 2012 session 214 "Core Data Best Practices" — BTW, if you go to the 13:20 mark in this video, you'll see that I'm using exactly the same performBlockAndWait: pattern shown there (except that I'm returning the results array, not an array of object IDs). And indeed, I use this pattern extensively to perform fetches and return the results, all without incident and all return the expected, sorted results: except in this one case and only under iOS 5.1. – Carl F. Hostetter Sep 27 '13 at 14:06
  • "But how else am I supposed to execute fetch requests and return their results in a thread-safe manner?" By passing blocks that handle results. "And indeed, I use this pattern extensively to perform fetches and return the results". Just because it looks like it works doesn't mean you are doing it right. Really, performBlockAndWait is only for reentrancy. Did you try performBlock with a semaphore? Is there a reason you can't pass in a block to operate on the query results? – quellish Oct 08 '13 at 09:10