[iOS] Strange behavior of NSFetchedResultsController
[iOS] Strange behavior of NSFetchedResultsController
- Subject: [iOS] Strange behavior of NSFetchedResultsController
- From: WT <email@hidden>
- Date: Thu, 3 Feb 2011 13:42:33 -0200
Hello list,
I'm experiencing a strange behavior on the part of NSFetchedResultsController and I can't seem to find why, although I suspect it's something utterly simple that I'm just not seeing.
Here's the setup. I have a list of some 20 keys the user can choose from to sort some experimental data by, and these keys are represented in a core data model. The custom subclass of NSManagedObject that encapsulates each key is called SortDescriptorCD and is defined as follows (other non-relevant stuff has been removed):
// SortDescriptorCD.h
#import <CoreData/CoreData.h>
// class statements
@interface SortDescriptorCD: NSManagedObject
{}
@property (readwrite, nonatomic, retain) NSNumber* index;
@property (readwrite, nonatomic, retain) NSString* title;
@property (readonly, nonatomic, retain) id indexOrTitle;
@property (readwrite, nonatomic, retain) NSNumber* canAppearOnList;
@property (readwrite, nonatomic, retain) NSNumber* doesAppearOnList;
@property (readonly, nonatomic, retain) NSNumber* canAndDoesAppearOnList;
// other property declarations
@end
// SortDescriptorCD.m
#import "SortDescriptorCD.h"
// other import statements
@implementation SortDescriptorCD
@dynamic index;
@dynamic title;
@dynamic canAppearOnList;
@dynamic doesAppearOnList;
// other dynamic declarations
- (id) indexOrTitle;
{
if ([self.canAndDoesAppearOnList isEqualToNumber:
[NSNumber numberWithBool: YES]])
{
return self.index;
}
else
{
return self.title;
}
}
- (NSNumber*) canAndDoesAppearOnList;
{
BOOL can = [self.canAppearOnList boolValue];
BOOL does = [self.doesAppearOnList boolValue];
return [NSNumber numberWithBool: (can && does)];
}
@end
Title isn't the actual key, but a human-readable version of it. These keys are sometimes not applicable to the data being shown, hence the canAppearOnList boolean. If they can, then whether or not they actually do is user-defined and that choice is stored in doesAppearOnList.
The terminology may seem a bit confusing because there are other things not being shown here. For instance, there is another pair of attributes, canSort and doesSort. The idea is that for those keys that canAppearOnList the user can select whether or not they want to see the information they represent (doesAppearOnList) and, additionally, whether that information is sorted or not (doesSort), assuming that it can be (canSort). And if it can be sorted and if the user chooses to sort by that key, whether it's sorted ascending or descending.
When the user de-selects a sorting key, its title gets grayed-out and its cell gets moved to a separate part of the tableview where it appears (it's not a separate tableview section, though I'm considering doing it that way). The selected part is sorted by index to allow the user to re-arrange the sorting order of the selected keys. The non-selected part is sorted by title. Tapping a row in one part moves its cell to the other part.
Now, the two attributes indexOrTitle and canAndDoesAppearOnList are defined in the core data model as transformable (optional and using the default transformer). The NSFetchedResultsController is defined as follows:
- (NSFetchedResultsController*) sortDescriptorCDs;
{
NSManagedObjectContext* context = self.managedObjectContext;
NSEntityDescription* entity = [NSEntityDescription
entityForName: @"SortDescriptorCD"
inManagedObjectContext: context];
NSSortDescriptor* sortByCanAndDoesAppearOnList = [[NSSortDescriptor alloc]
initWithKey: @"canAndDoesAppearOnList" ascending: NO];
// Replacing @"indexOrTitle" with @"index" or @"title" works fine.
NSSortDescriptor* sortByIndexOrTitle = [[NSSortDescriptor alloc]
initWithKey: @"indexOrTitle" ascending: YES];
NSArray* sortDescriptors = [[NSArray alloc] initWithObjects:
sortByCanAndDoesAppearOnList, sortByIndexOrTitle, nil];
[sortByCanAndDoesAppearOnList release];
[sortByIndexOrTitle release];
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: entity];
[fetchRequest setSortDescriptors: sortDescriptors];
[fetchRequest setFetchBatchSize: 20];
[sortDescriptors release];
NSFetchedResultsController* frc = [[NSFetchedResultsController alloc]
initWithFetchRequest: fetchRequest
managedObjectContext: context
sectionNameKeyPath: nil
cacheName: nil];
[fetchRequest release];
NSError* error = nil;
if (! [frc performFetch: &error])
{
// TODO - Handle fetching error.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return [frc autorelease];
}
Note the comment
// Replacing @"indexOrTitle" with @"index" or @"title" works fine.
which makes me confident that sortDescriptorCDs is implemented correctly. By working fine I mean that the tableview does display all the keys sorted correctly either by their indices or by their titles, depending on the replacement.
However, using @"indexOrTitle" seems to produce a random order for the fetched results (random in that there's no obvious pattern to the resulting sort order). Moreover, I verified that -indexOrTitle is not invoked at all in this case. -canAndDoesAppearOnList is invoked, as expected, once per entity when the fetch is performed.
Even stranger is that I have another situation similar to this one in the same project and it works correctly. The various attributes are defined and everything is implemented exactly the same way; the only differences I can see are in the names of the various attributes and classes.
Here's the code for the situation that works without a glitch (again, non-relevant stuff has been removed):
// ExperimentCD.h
#import <CoreData/CoreData.h>
// class statements
@interface ExperimentCD: NSManagedObject
{}
@property (readwrite, nonatomic, retain) NSString* displayName;
@property (readwrite, nonatomic, retain) NSNumber* userSelected;
@property (readwrite, nonatomic, retain) NSNumber* userOrderIndex;
@property (readonly, nonatomic, retain) id userOrderIndexOrDisplayName;
// other property declarations
@end
// interface for CoreDataGeneratedAccessors
// ExperimentCD.m
#import "ExperimentCD.h"
// other import statements
@implementation ExperimentCD
@dynamic displayName;
@dynamic userSelected;
@dynamic userOrderIndex;
// other dynamic statements
- (id) userOrderIndexOrDisplayName;
{
if ([self.userSelected isEqualToNumber: [NSNumber numberWithBool: YES]])
{
return self.userOrderIndex;
}
else
{
return self.displayName;
}
}
@end
- (NSFetchedResultsController*) allExperimentCDsFRC;
{
NSManagedObjectContext* context = self.managedObjectContext;
NSEntityDescription* entity = [NSEntityDescription
entityForName: @"ExperimentCD"
inManagedObjectContext: context];
NSSortDescriptor* sortByUserSelected = [[NSSortDescriptor alloc]
initWithKey: @"userSelected" ascending: NO];
NSSortDescriptor* sortByUserOrderIndexOrDisplayName =
[[NSSortDescriptor alloc] initWithKey:
@"userOrderIndexOrDisplayName" ascending: YES];
NSArray* sortDescriptors = [[NSArray alloc] initWithObjects:
sortByUserSelected, sortByUserOrderIndexOrDisplayName, nil];
[sortByUserSelected release];
[sortByUserOrderIndexOrDisplayName release];
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: entity];
[fetchRequest setSortDescriptors: sortDescriptors];
[fetchRequest setFetchBatchSize: 10];
[sortDescriptors release];
NSFetchedResultsController* frc = [[NSFetchedResultsController alloc]
initWithFetchRequest: fetchRequest
managedObjectContext: context
sectionNameKeyPath: nil
cacheName: nil];
[fetchRequest release];
NSError* error = nil;
if (! [frc performFetch: &error])
{
// TODO - handle fetching error
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return [frc autorelease];
}
In this second case, the user can select a group of experiments to monitor and it's the same mechanism as before. Tapping on the row for a selected experiment deselects it and vice-versa, and the user can rearrange the order of the rows for the selected experiments. The selected ones are sorted by index, the unselected ones are sorted by their display names.
This second case works just fine. I copied the implementation of this case when writing the code for the first case, and the corresponding attributes in the core data model are all defined similarly, so I can't figure out why the first case isn't working. The biggest clue is that -indexOrTitle isn't being invoked, but I can't see why not.
Can someone please shed some light on this?
Thanks in advance.
WT_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden