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
allocWithZone:
instead ofalloc
in yourcopyWithZone:
? – Cut