Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom sorting with NSFetchedResultController (subclassing NSSortDescriptor)

I want to provide custom sorting using NSFetchedResultsController and NSSortDescriptor.

As custom sorting via NSSortDescriptor message -(id)initWithKey:ascending:selector: is not possible (see here), I tried to use a NSSortDescriptor derived class in order to override the compareObject:toObject: message.

My problem is that the compareObject:toObject: is not always called. It seems that it is called only when the data are already in memory. There is an optimization of some sort that use a database based sort instead of the compareObject:toObject when the data are retrieved from the store the first time. (see here).

My question is : how to force NSFetchedResultscontroller to use the compareObject:toObject: message to sort the data ? (and will it work with large data set)

One solution is to use a binary store instead of a sqlite store but I don't want to do that.

Another solution is:
-call performFetch to sort data via SQL (compareObject not called)
-make a modification to the data and reverse it.
-call performFetch again (compareObject is called)
It does work in my case but it's a hack and I am not sure it will always work (especially with large data set (greater than the batch size)).

UPDATED:You can reproduce with the CoreDataBooks sample.
In RootViewController.m, add this ugly hack:

- (void)viewWillAppear:(BOOL)animated {
    Book* book = (Book *)[NSEntityDescription insertNewObjectForEntityForName:@"Book" 
                 inManagedObjectContext:[self fetchedResultsController].managedObjectContext];
    [[self fetchedResultsController] performFetch:nil];
    [[self fetchedResultsController].managedObjectContext deleteObject:book];
    [self.tableView reloadData];
}

In RootViewController.m, replace the sort descriptor code with:

MySortDescriptor *myDescriptor = [[MySortDescriptor alloc] init];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:myDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];  

Add MySortDescriptor class:

@implementation MySortDescriptor

-(id)init
{
    if (self = [super initWithKey:@"title" ascending:YES selector:@selector(compare:)])
    {

    }
    return self;
}

- (NSComparisonResult)compareObject:(id)object1 toObject:(id)object2
{
    //set a breakpoint here
    return [[object1 valueForKey:@"author" ] localizedCaseInsensitiveCompare:[object2 valueForKey:@"author" ] ];
}

//various overrides inspired by [this blog post][3]
- (id)copy
{
    return [self copyWithZone:nil ];
}
- (id)mutableCopy
{
    return [self copyWithZone:nil ];
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
    return [self copyWithZone:zone ];
}
- (id)copyWithZone:(NSZone*)zone
{
    return [[MySortDescriptor alloc] initWithKey:[self key] ascending:[self ascending] selector:[self selector]];
}
- (id)reversedSortDescriptor
{
    return [[[MySortDescriptor alloc] initWithKey:[self key] ascending:![self ascending] selector:[self selector]] autorelease];
}
@end
like image 836
FKDev Avatar asked Nov 05 '10 13:11

FKDev


1 Answers

In reference to your question and the comments. You are going to need to pull the objects into memory to sort them. Once they are in memory you can use a convenience method to determine distance from a point.

To decrease the number of objects you pull into memory you could calculate max and min values and then filter on those, reducing the radius of your search before you sort.

It is not possible to sort on a calculated value unless it is in memory.

like image 67
Marcus S. Zarra Avatar answered Sep 28 '22 13:09

Marcus S. Zarra