Does anyone know why I cannot save a change in the order of objects in an NSOrderedSet relationship in Core Data? I already know of the bug where using the generated functions for an NSOrderedSet relationship does not work well, and so I always use the keypath. Below is the code for two functions in a tableview.
The tableView:moveRowAtIndexPath:toIndexPath: claims to save successfully, but the change is actually not saved, meaning if I perform a [tableView reloadData];, the old order is still there. Even if I exit the app and restart it, the order has not changed. I have verfified this with multiple NSLogs. The second function, tableView:commitEditingStyle:forRowAtIndexPath:, which I use to delete an NSOrderedSet entry, works perfectly.
My theory is that Core Data discards the order information from the NSOrderedSet relationship in its internal representation, and so because the objects remain the same, it figures it doesn't need to save anything. Has anyone experienced anything like this before? If so, did you find a work-around?
-(void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
NSManagedObjectContext *context= [self managedObjectContext];
Playlist *playlist = (Playlist*) [context objectWithID:playlistID];
[playlist willChangeValueForKey:#"tracks"];
NSMutableOrderedSet *exchange = [playlist mutableOrderedSetValueForKey:#"tracks"];;
NSInteger fromIndex = sourceIndexPath.row;
NSInteger toIndex = destinationIndexPath.row;
NSMutableArray *arrayOfTracks = [NSMutableArray arrayWithArray:[exchange array]];
[arrayOfTracks exchangeObjectAtIndex:fromIndex withObjectAtIndex:toIndex];
[[playlist mutableOrderedSetValueForKey:#"tracks"] removeAllObjects];
[[playlist mutableOrderedSetValueForKey:#"tracks"] addObjectsFromArray:arrayOfTracks];
playlist.md5Hash = nil;
[playlist didChangeValueForKey:#"tracks"];
NSError *savingError = nil;
if ([context save:&savingError]){
NSLog(#"Successfully saved the context for reorder");
} else {
NSLog(#"Failed to save the context. Error = %#", savingError); }
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectContext *context= [self managedObjectContext];
Playlist *playlist = (Playlist*) [context objectWithID:playlistID];
Track *track = [self.tracksFRC objectAtIndexPath:indexPath];
NSMutableOrderedSet *exchange = [NSMutableOrderedSet orderedSetWithOrderedSet: playlist.tracks];
[exchange removeObject:track];
[track removeBelongingPlaylistObject:playlist];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [playlist.tracks count])];
[[playlist mutableOrderedSetValueForKey:#"tracks"] replaceObjectsAtIndexes:indexSet withObjects:[exchange array]];
playlist.md5Hash = nil;
NSError *savingError = nil;
if ([context save:&savingError]){
NSLog(#"Successfully saved the context for remove entry");
} else {
NSLog(#"Failed to save the context. Error = %#", savingError); }
}
}
EDIT: I was able to solve the problem by calling [context save:&savingError] twice, once with the records removed, and once with them reinserted in the new order. This fix shouldn't really be necessary though.
What's the reason for converting to an array? NSMutableOrderedSet already supports the method exchangeObjectAtIndex:withObjectAtIndex:
I'd suggest:
NSMutableOrderedSet *exchange = [playlist.tracks mutableCopy];
NSInteger fromIndex = sourceIndexPath.row;
NSInteger toIndex = destinationIndexPath.row;
[exchange exchangeObjectAtIndex:fromIndex withObjectAtIndex:toIndex];
playlist.tracks = exchange;
Also, you seem to have a many to many relationship between Playlist and Track, so your delete method works because of your call to [track removeBelongingPlaylistObject:playlist]; Everything else in the method should be redundant (except for saving the context).
Converting #ikuramedia's code to swift gives:
var exchange: NSMutableOrderedSet = playlist.tracks.mutableCopy() as! NSMutableOrderedSet
exchange.exchangeObjectAtIndex(fromIndexPath.row, withObjectAtIndex: toIndexPath.row)
playlist.tracks = exchange
In case anyone needs it
Related
I've just finished re-writing this, and covered every conceivable angle I can think of. I don't know why this is crashing. Perhaps somebody could help me figure it out.
This is the cellForRowAtIndexPath code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
JHomeViewCell *cell = (JHomeViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[JHomeViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
cell.delegate = self;
}
cell.cellContent.cellInfo = [self cellInfoForCellAtIndexPath:indexPath];
if (cell.cellContent.cellInfo.thumbnailsComplete == YES || cell.cellContent.cellInfo.thumbnailsBeingCreated == YES) {
[cell.cellContent setNeedsDisplay];
}
else {
[cell.cellContent setup];
}
return cell;
}
And in cellContent, there's this setup method:
-(void)setup {
[self setNeedsDisplay];
self.cellInfo.thumbnailsBeingCreated = YES;
NSManagedObjectID *entryID = self.cellInfo.objectID;
dispatch_queue_t cellSetupQueue = dispatch_queue_create("com.Journalized.SetupCell", NULL);
dispatch_async(cellSetupQueue, ^{
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coordinator = [[CoreDataStore mainStore] context].persistentStoreCoordinator;
[newMoc setPersistentStoreCoordinator:coordinator];
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
Entry *entry = (Entry *)[newMoc objectWithID:entryID];
[newMoc save:nil];
int i = 0;
while (i < self.cellInfo.numberOfThumbnailsToDraw) {
NSLog(#"number of thumbnails: %i %i %i", self.cellInfo.numberOfThumbnailsToDraw, entry.media.count, i);
Media *media = [entry.media objectAtIndex:i];
UIImage *image = [media getThumbnail];
BOOL success = [newMoc save:nil];
//NSLog(#"time: %# success: %i", entry.entryTableInfo.creationTimeString, success);
[self.cellInfo.thumbnails setObject:image forKey:[NSNumber numberWithInt:i]];
i++;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
dispatch_async(dispatch_get_main_queue(), ^{
self.cellInfo.thumbnailsComplete = YES;
[self setNeedsDisplay];
});
});
dispatch_release(cellSetupQueue);
It crashes on the line:
Media *media = [entry.media objectAtIndex:i];
With the error:
index 1 beyond bounds [0 .. 0]
The NSLog above that...
NSLog(#"number of thumbnails: %i %i %i", self.cellInfo.numberOfThumbnailsToDraw, entry.media.count, i);
Gives the result:
number of thumbnails: 2 1 1
Which sort of explains the crash, except that value is set in the [cellInfoForCellAtIndexPath:]; method, like so:
cellInfo.numberOfMediaItems = entry.media.count;
cellInfo.numberOfThumbnailsToDraw = MIN(cellInfo.numberOfMediaItems, 3);
I really don't know where the problem is occurring, or why it's occurring, but I can't move on with my app until this part is fixed.
Well numberOfThumbnailsToDraw is 2 meaning the while loop will do 0, 1, but the count of your entry.media is only 1 so it only has a 0 index so of course it'll crash.
[managedObjectContext obtainPermanentIDsForObjects:self.cellInfo error:nil];
[managedObjectContext save:...];
NSManagedObjectID *entryID = self.cellInfo.objectID;
You need to make sure that
1. You have a permanent object ID; not a temporary one
2. The object is persisted so that it appears on the new MOC.
It looks like you are querying entry.media.count where entry is a pointer into one MOC, and then you are querying it from another. You are asking using the objectID, which is reasonable.
However, when the new MOC gets the object, it does not see the same values as you saw in the other MOC. Most likely, this means that you have not properly saved the other MOC.
What happens if you execute a fetch for the object on the new MOC?
Also, I would enable core data debugging (-com.apple.CoreData.SQLDebug 1) in command line options. This will log to the console what's going on underneath. You should see the SQL statements for the underlying database logged to the console.
Also, you are saving your MOC without ever making any changes to it, which leads me to believe you are a bit confused on how your many MOCs are working together.
Currently, I have edited a delegate function that adds Exercise objects to an NSMutableArray. However, I would not like to add duplicate objects, instead, if the object is already in the array, i'd like to simply access that particular object.
Here is my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *str = cell.textLabel.text; // Retrieves the string of the selected cell.
Exercise *exerciseView = [[Exercise alloc] initWithExerciseName:str];
WorkoutManager *workoutManager = [WorkoutManager sharedInstance];
if (![[workoutManager exercises] containsObject:exerciseView]) {
[[workoutManager exercises] insertObject:exerciseView atIndex:0];
[self presentModalViewController:exerciseView animated:YES];
NSLog(#"%#", [workoutManager exercises]);
}
else {
[self presentModalViewController:exerciseView animated:YES];
NSLog(#"%#", [workoutManager exercises]);
}
}
I thought this would work, however, when I ran my code and NSLogged my array, it showed that when I clicked on the same cell, two seperate objects were created. Any help?
Each time you call
Exercise *exerciseView = [[Exercise alloc] initWithExerciseName:str];
it create a new (distinct) exerciseView object. So even though the exercise name may be the same as the name for an exercise object in your exercises list, it is a brand new object so when you call containsObject the result will always be false and your new object will be added to the array.
Perhaps you should store a list of the NSString exerciseName in your workout manager instead?
I would say this is your culprit:
Exercise *exerciseView = [[Exercise alloc] initWithExerciseName:str];
You're creating a new object each time so technically, it's not in the array. The containsObject method is just iterating through the array and calling isEqual on each object. I haven't tested this but theoretically, in your custom Exercise object, you could override the isEqual method to compare the exercise name properties and return true if they match. See, EVERYTHING has to match up when you are using containsObject so even if all the properties are the same, the objectid is not.
Easy fix without having to see your Exercise implementation:
Exercise *exerciseView = nil;
For(Exercise *exercise in [[WorkoutManager sharedInstance] exercises]){
if(exercise.exerciseName == str) {
exerciseView = exercise;
break;
}
}
if(exerciseView == nil) {
exerciseView = [[Exercise alloc] initWithExerciseName:str];
[[workoutManager exercises] insertObject:exerciseView atIndex:0];
}
[self presentModalViewController:exerciseView animated:YES];
Hope this helps explain WHY its happening. I didn't test this code since there are some missing pieces but you should get the idea. Have fun!
WorkoutManager *workoutManager = [WorkoutManager sharedInstance];
Exercise *temp = [[Exercise alloc] initWithExerciseName:str];
for(id temp1 in workoutManager)
{
if( [temp isKindOfClass:[Exercise class]])
{
NSLog(#"YES");
// You Can Access your same object here if array has already same object
}
}
[temp release];
[workoutManager release];
Hope, this will help you....
I am trying to override tableView:commitEditingStyle:editingStyleforRowAtIndexPath: and having trouble implementing the deletion of the actual instance of a NSManagedObject that is represented in that row.
Apple says it should be done with the following code(Shown Here):
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object at the given index path.
NSManagedObject *eventToDelete = [eventsArray objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:eventToDelete];
// Update the array and table view.
[eventsArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
// Commit the change.
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
}
}
When I mimick this sample code in my app, every line works except for one line. The one line is: [bowlerArray removeObjectAtIndex:indexPath.row];. I get the error "Receiver type 'NSArray' for instance message does not declare a method with selector 'removeObjectAtIndex'".
What should that one line of code be?
Note: My line NSManagedObject *eventToDelete = [bowlerArray objectAtIndex:indexPath.row]; works just fine.
Update: Posting my actual code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [appDelegate managedObjectContext];
NSManagedObject *objectToDelete = [bowlerArray objectAtIndex:indexPath.row];
[moc deleteObject:objectToDelete];
[bowlerArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
NSArray is immutable, so you can't modify it.
removeObjectAtIndex is not part of the NSArray API, because it would modify it.
You need an NSMutableArray to be able to do that.
If I do this :
NSMutableArray *arMu = [NSMutableArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", nil];
[arMu removeObjectAtIndex:0];
self.bigLabel.text = [arMu objectAtIndex:0];
the bigLabel is showing 1 for the index 0.
You error message is suggesting that you still have a NSArray instead of an NSMutableArray for the variable eventsArray
You can make a NSMutableArray from a NSArray like this :
NSMutableArray *arMu = [[NSMutableArray alloc] initWithArray:someNSArray];
I've a simple applications that lets you create groups of people form persons in your AddressBook... So Groups and Persons are in a one-to-many relationships, since a Group can have multiple persons. That's not a many-to-many since I create my own model of Person.
Adding data works without problems.
Deleting data doesn't. If I create a new Person, I must restart the app to delete it or the to delete the Group that Person belongs. Otherwise I get a "EXC BAD ACCESS" in the console. With NSZombieEnabled in the enviroment I get -[CFString release]: message sent to deallocated instance 0x75140d0.
I start with the CoreData stuff automatically created by XCode, create the RootViewController (subclass of TableViewController), I pass it the context and put it in a NavigationController.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Creo il controller root e gli passo il context
RootViewController *rvc = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
rvc.context = [self managedObjectContext];
//Creo il navcon, gli associo il root e lo rendo visibile
navCon = [[UINavigationController alloc] initWithRootViewController:rvc];
[window addSubview:navCon.view];
[window makeKeyAndVisible];
[rvc release];
return YES;
}
The RootViewController shows the Groups, then clicking on a row lets you modify persons in that group, passing the "nuovogruppo" (the Group Model associated with that row)
- (void)showPersoneControllerWithGruppo:(Gruppo *)nuovogruppo {
PersoneController *pc = [[PersoneController alloc] initWithStyle:UITableViewStylePlain];
pc.gruppo = nuovogruppo;
pc.context = self.context;
pc.delegate = self;
//NSLog(#"%#",[gruppi objectAtIndex:indexPath.row]);
[self.navigationController pushViewController:pc animated:YES];
[pc release];
}
And this is how I delete the person (gruppo is the Group model these persons belong to, persone is an array filled with these persons on viewDidLoad, removeGPObject is an accessor method generated by XCode (Group to Persons relationship))
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
}
}
I hope someone can help me...
UPDATE
Since I was having errors about messages sent to already released instances I tried commenting out all the [... release] lines and finally find out what was causing the problem. The problem was in the creation method of the record and not in the deleting method. Here is the method I use to create it.
The line that was causing the roblem is [NomeCognome release]
I'd be very grateful if someone could explain me why this line crashes the app.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
if (property == kABPersonPhoneProperty) {
ABMultiValueRef phoneProperty = ABRecordCopyValue(person,property);
NSString *phone = (NSString *)ABMultiValueCopyValueAtIndex(phoneProperty,identifier);
NSString *firstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *surname = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *NomeCognome = [NSString stringWithFormat:#"%# %#", firstName, surname];
[firstName release];
[surname release];
Persona *persona = (Persona *)[NSEntityDescription insertNewObjectForEntityForName:#"Persona" inManagedObjectContext:context];
persona.ABRR = phone;
persona.NomeCognome = NomeCognome;
[phone release];
[NomeCognome release]; //This line makes the app crash!!! Why???
[gruppo addGPObject:persona];
NSError *error;
[context save:&error];
[self.delegate PersoneControllerDidSave:self];
[self loadContentAndReload:YES];
[self dismissModalViewControllerAnimated:YES];
}
Why that line crashes the app?
Use reloadData method at the end as the following.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
[tableView reloadData];
}
It may work now.
I've attempted to add the TopSongs parser and Core Data files into my application, and it now builds succesfully, with no errors or warning messages. However, as soon as the app loads, it crashes, giving the following reason:
UPDATE: I've got it all working, but my TableView doesn't show any data, and the app doesn't respond to the following breakpoints.
Thanks.
UPDATE: Here's the new code that doesn't respond to the breakpoints.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)table {
return [[fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (void)viewDidUnload {
[super viewDidUnload];
self.tableView = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *kCellIdentifier = #"SongCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
}
Incident *incident = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:NSLocalizedString(#"#%d %#", #"#%d %#"), incident.title];
return cell;
}
- (void)tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[table deselectRowAtIndexPath:indexPath animated:YES];
self.detailController.incident = [fetchedResultsController objectAtIndexPath:indexPath];
[self.navigationController pushViewController:self.detailController animated:YES];
}
UPDATE: Here's the code where all instances of fetch are found.
- (Category *)categoryWithName:(NSString *)name {
NSTimeInterval before = [NSDate timeIntervalSinceReferenceDate];
#ifdef USE_CACHING
// check cache
CacheNode *cacheNode = [cache objectForKey:name];
if (cacheNode != nil) {
// cache hit, update access counter
cacheNode.accessCounter = accessCounter++;
Category *category = (Category *)[managedObjectContext objectWithID:cacheNode.objectID];
totalCacheHitCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheHitCount++;
return category;
}
#endif
// cache missed, fetch from store - if not found in store there is no category object for the name and we must create one
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:self.categoryEntityDescription];
NSPredicate *predicate = [self.categoryNamePredicateTemplate predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:name forKey:kCategoryNameSubstitutionVariable]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
NSAssert1(fetchResults != nil, #"Unhandled error executing fetch request in import thread: %#", [error localizedDescription]);
Category *category = nil;
if ([fetchResults count] > 0) {
// get category from fetch
category = [fetchResults objectAtIndex:0];
} else if ([fetchResults count] == 0) {
// category not in store, must create a new category object
category = [[Category alloc] initWithEntity:self.categoryEntityDescription insertIntoManagedObjectContext:managedObjectContext];
category.name = name;
[category autorelease];
}
#ifdef USE_CACHING
// add to cache
// first check to see if cache is full
if ([cache count] >= cacheSize) {
// evict least recently used (LRU) item from cache
NSUInteger oldestAccessCount = UINT_MAX;
NSString *key = nil, *keyOfOldestCacheNode = nil;
for (key in cache) {
CacheNode *tmpNode = [cache objectForKey:key];
if (tmpNode.accessCounter < oldestAccessCount) {
oldestAccessCount = tmpNode.accessCounter;
[keyOfOldestCacheNode release];
keyOfOldestCacheNode = [key retain];
}
}
// retain the cache node for reuse
cacheNode = [[cache objectForKey:keyOfOldestCacheNode] retain];
// remove from the cache
[cache removeObjectForKey:keyOfOldestCacheNode];
} else {
// create a new cache node
cacheNode = [[CacheNode alloc] init];
}
cacheNode.objectID = [category objectID];
cacheNode.accessCounter = accessCounter++;
[cache setObject:cacheNode forKey:name];
[cacheNode release];
#endif
totalCacheMissCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheMissCount++;
return category;
}
And this one...
- (void)fetch {
NSError *error = nil;
BOOL success = [self.fetchedResultsController performFetch:&error];
NSAssert2(success, #"Unhandled error performing fetch at SongsViewController.m, line %d: %#", __LINE__, [error localizedDescription]);
[self.tableView reloadData];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Song" inManagedObjectContext:managedObjectContext]];
NSArray *sortDescriptors = nil;
NSString *sectionNameKeyPath = nil;
if ([fetchSectioningControl selectedSegmentIndex] == 1) {
sortDescriptors = [NSArray arrayWithObjects:[[[NSSortDescriptor alloc] initWithKey:#"category.name" ascending:YES] autorelease], [[[NSSortDescriptor alloc] initWithKey:#"rank" ascending:YES] autorelease], nil];
sectionNameKeyPath = #"category.name";
} else {
sortDescriptors = [NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"rank" ascending:YES] autorelease]];
}
[fetchRequest setSortDescriptors:sortDescriptors];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:sectionNameKeyPath cacheName:#"SongsCache"];
}
return fetchedResultsController;
}
your extra caching is probably a waste of cycles as Core Data performs its own caching internally. I am willing to bet you are slowing things down rather than speeding them up, not to mention the additional memory you are consuming.
Where are you setting categoryEntityDescription? That is now shown in the code you posted. It is probably nil.
Why are you retaining an NSEntityDescription?!? They are already in memory because of Core Data and retaining them is a waste which could lead to issues if Core Data wants to release it at some point.
update
Your caching is definitely not coming from Apple's code because they know that the cache is in Core Data.
As for the NSEntityDescription, again, do not retain the NSEntityDescription.
Are you 100% positive that the NSEntityDescription is not nil? Have you confirmed it in the debugger? Have you tested it with a freshly retrieved NSEntityDescription?
update
You need to learn to use the debugger as that will solve most of your coding issues. Put a breakpoint in this method and run your code in the debugger. Then when the execution stops on that break point you can inspect the values of the variables and learn what they are currently set to. That will confirm or deny your suspicions about what is and is not nil.
This error you are seeing happens when you fail to set the Entity in the NSFetchRequest which, based on your code, means that retained property is not being set before the code you have shown is being called.
Based on the code posted and the problem description, I suspect that the categoryEntityDescription property is returning nil.
I've seen this happen when the NSEntityDescription given to a fetch request is nil. The most likely cause of that is that you have a model entity that is named differently from the name you provided to entityForName. Barring that, it could be an error in configuration of your Core Data stack or a missing data model, but as a first step, I would recommend storing the result of entityForName in a local variable and breaking there to make sure it isn't nil.
Since you added the model file manually, is the .xcdatamodel file inside the Compile Sources step in your Target?
Go to the Targets entry in the Groups & Files pane in Xcode and click the disclosure triangle. Then click on the disclosure triangle for your app. Then check to see if it's in Compile Sources. If not, right click on Compile Sources and choose "Add -> Existing File..." and add it.
Edit based on update:
UPDATE: Here's the new code that
doesn't respond to the breakpoints.
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (void)tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Is your view controller set as the UITableViewDataSource/UITableViewDelegate for your UITableView? If not, these methods will not get called.