Can't understand why my tableview with GCD is crashing - iphone

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.

Related

UIProgressView indicator not updating with the correct "progress"

I am downloading images asynchronously and displaying them in a UITableView. While theimage is downloading, a UIProgressView should be displayed in the corresponding table row. After the download is complete, progress view should be replaced by the actual image.
In my table view, I am using a custom cell called ProgressTableViewCell subclassed from UITableViewCell. It has a UIProgressView IBOutlet.
I have created an NSOperation from NSURLConnection and added them to an NSOperationQueue. As the delegate's
didReceiveData
method is called, a notification is posted to my table view controller to update the corresponding table row with
reloadRowsAtIndexPaths
method of table view. My cellForRowAtIndexPath does the following for the reloaded row:
ProgressTableViewCell *cell = (ProgressTableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"ProgressCell"];
float received = [[downloadInfo objectForKey:#"receivedBytes"] floatValue];
float total = [[downloadInfo objectForKey:#"totalFileSize"] floatValue];
NSNumber* percentage= [NSNumber numberWithFloat:received/total];
NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
NSLog(#"percentage %f", percentage.floatValue);
[userInfo setObject:cell forKey:#"cell"];
[userInfo setObject:percentage forKey:#"percentage"];
[self performSelectorOnMainThread:#selector(updateProgressView:) withObject:userInfo waitUntilDone:NO];
NSLog(#"received: %#", [downloadInfo objectForKey:#"receivedBytes"]);
NSLog(#"Progress: %f", cell.progressView.progress);
return cell;
The updateProgressView method looks like
- (void)updateProgressView :(NSMutableDictionary *)userInfo
{
ProgressTableViewCell* cell = [userInfo valueForKey:#"cell"];
NSNumber* progress = [userInfo valueForKey:#"percentage"];
[cell.progressView setProgress:progress.floatValue ];
NSLog(#"Progress after update: %f", cell.progressView.progress);
}
I am updating the progress view on the main thread and I have even tried setting waitUntilDone to YES but to no avail. My progress view stays at the zero point. Occasionally when I am debugging I can see some change in the progress indicator which makes me think it might be a timing problem. But how to solve it?
EDIT: Here is NSURLConnection delegate's didReceiveData method:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_responseData appendData:data];
NSNumber* bytes = [NSNumber numberWithUnsignedInt:[data length]];
NSLog(#"received bytes:%d", [bytes intValue] );
NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
[userInfo setObject:_responseId forKey:#"responseId"];
[userInfo setObject:bytes forKey:#"receivedBytes"];
[self fireNotification: [NSNotification
notificationWithName:#"DidReceiveData"
object:self userInfo:userInfo]];
}
- (void)fireNotification :(NSNotification *)aNotification
{
[[NSNotificationCenter defaultCenter] postNotification:aNotification];
}
And here is my view controller's method that gets the notification:
-(void) dataReceived:(NSNotification *)notification {
NSNumber* responseId = [[notification userInfo] objectForKey:#"responseId"];
NSNumber* bytes = [[notification userInfo] objectForKey:#"receivedBytes"];
NSMutableDictionary* downloadInfo = [self getConnectionInfoForId:responseId];
NSLog(#"received bytes:%ld for response %#", [bytes longValue], responseId );
NSNumber* totalBytes = [NSNumber numberWithInt:([bytes longValue] + [[downloadInfo objectForKey:#"receivedBytes"] longValue]) ];
[downloadInfo setObject:totalBytes forKey:#"receivedBytes"];
float received = [[downloadInfo objectForKey:#"receivedBytes"] floatValue];
float total = [[downloadInfo objectForKey:#"totalFileSize"] floatValue];
[downloadInfo setObject:[NSNumber numberWithFloat:received/total] forKey:#"progress"];
[self reloadRowForResponse:responseId];
}
I have also added a nil check to my cellForRowAtIndexpath method as recommended:
ProgressTableViewCell *cell = (ProgressTableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"ProgressCell"];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"ProgressCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
float received = [[downloadInfo objectForKey:#"receivedBytes"] floatValue];
float total = [[downloadInfo objectForKey:#"totalFileSize"] floatValue];
NSNumber* percentage= [NSNumber numberWithFloat:received/total];
NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] init];
NSLog(#"cell:%#", cell);
NSLog(#"percentage %f", percentage.floatValue);
[userInfo setObject:cell forKey:#"cell"];
[userInfo setObject:percentage forKey:#"percentage"];
[self performSelectorOnMainThread:#selector(updateProgressView:) withObject:userInfo waitUntilDone:NO];
return cell;
I think you're taking the wrong approach by reloading the table cell every time the delegate method gets called. You can instead just grab the visible cell and update the progress indicator directly, rather than going through the data source.
I'm assuming you have some way of converting responseId to the index path of the row you want to update -- let's say that's called indexPathForResponseID: in your controller. Rather than reloading the cell, you can just grab the cell if it's visible and update its progress indicator:
- (void)dataReceived:(NSNotification *)notification {
...
float received = [[downloadInfo objectForKey:#"receivedBytes"] floatValue];
float total = [[downloadInfo objectForKey:#"totalFileSize"] floatValue];
NSIndexPath *cellPath = [self indexPathForResponseID:responseId];
ProgressTableViewCell *cell = (ProgressTableviewCell *)[self.tableView cellForRowAtIndexPath:cellPath];
if (cell) {
// make sure you're on the main thread to update UI
dispatch_async(dispatch_get_main_queue(), ^{
[cell.progressView setProgress: (received / total)];
}
}
}
You should be aware, though, that this solution won't suffice if you have more downloads than visible cells -- you should also be storing the progress of each download somewhere in your data source so that if the table view DOES need to reload a cell (due to a scroll), it knows how to set the progress indicator.
I've found in many cases like this that the cell you think you have isn't the one that's updating. When you reload, your code is popping a cell off the reusable, which is basically an old cell. If you reload, that cell gets replaced by another. (You also haven't included allocating a new cell if the reusable ones return nil) If your cell scrolls off, it gets reloaded, so you have to make sure that you're putting the progress bar on the cell that's there, not the one that used to be there. You might want to NSLog the address of the cell you started the transfer with, and then again each time you update the progress. You need to update the progress bar with the current cell at that index path. It may not be the one you initially started the process with.
Coming back to this almost 2 months later, I seem to find a solution. Not sure if this is good practice but creating a new UIProgressView each time the progress is updated seems to solve my problem. Here is the method:
-(void)updateProgressView :(NSMutableDictionary *)userInfo
{
ProgressTableViewCell* cell = [userInfo objectForKey:#"cell"];
cell.backgroundColor =[UIColor darkGrayColor];
NSNumber* progress = [userInfo objectForKey:#"percentage"];
NSLog(#"Progress before update: %f", cell.progressView.progress);
UIProgressView *pView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
pView.frame = CGRectMake(150, 200, 150, 9);
pView.progress = progress.floatValue;
[cell.contentView addSubview:pView];
}
Many thanks to everyone for their help.

UITableView application crashes when scrolling

Firstly, please forgive my English.
I am learning about iPhone SDK, Obj-C and trying to make UITableView app. But my app crashes when scrolling the table. I've searched but can't solve my problem, so I post my question here and look for your help.
TMTMemberListTableViewController.h
#interface TMTMemberListTableViewController : UITableViewController
{
NSMutableArray *familyMembers;
sqlite3 *database;
}
#property (nonatomic, retain) NSMutableArray *familyMembers;
- (void) configureCell:(TMTFamilyMemberCell *)cell withFamilyMember:(TMTFamily *)member;
#end
TMTMemberListTableViewController.m
#interface TMTMemberListTableViewController (Private)
- (void) loadMemberListFromDatabase;
#end
#implementation TMTMemberListTableViewController
#synthesize familyMembers;
- (void) loadMemberListFromDatabase
{
familyMembers = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"easycook.sqlite"];
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
const char *sql = "SELECT member_id from family";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *memberID = [NSString stringWithUTF8String:(char *) sqlite3_column_text(statement, 0)];
TMTFamily *member = [[TMTFamily alloc] initWithMemberID:memberID database:database];
[familyMembers addObject:member];
[member release];
}
}
sqlite3_finalize(statement);
}
else
{
sqlite3_close(database);
NSAssert1(0, #"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.clearsSelectionOnViewWillAppear = NO;
self.navigationItem.rightBarButtonItem = self.editButtonItem;
[self loadMemberListFromDatabase];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [familyMembers count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//static NSString *MyIdentifier = #"MyIdentifier";
TMTFamilyMemberCell *cell = (TMTFamilyMemberCell *)[tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil)
{
cell = [[[TMTFamilyMemberCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
TMTFamily *member = [familyMembers objectAtIndex:indexPath.row];
// Set up the cell
[self configureCell:cell withFamilyMember:member];
[member release];
return cell;
}
- (void) configureCell:(TMTFamilyMemberCell *)cell withFamilyMember:(TMTFamily *)member
{
cell.familyMember = member;
cell.memberNameLabel.text = member.description;
}
- (void)dealloc
{
NSLog(#"Dealloc");
[familyMembers release];
[super dealloc];
}
When scrolling table, my app crashes with EXC_BAD_ACCESS at
[self configureCell:cell withFamilyMember:member];
and
cell.memberNameLabel.text = member.description;
This line is not incrementing retain counter:
TMTFamily *member = [familyMembers objectAtIndex:indexPath.row]
So the following line is your problem
[member release];
You cannot release object if you down't own it. Either retain it like that:
TMTFamily *member = [[familyMembers objectAtIndex:indexPath.row] retain];
or
TMTFamily *member = [[familyMembers objectAtIndex:indexPath.row] copy];
or remove mentioned release completelly.
First off, welcome to Stack Overflow and welcome to the world of iOS Development.
There are a bunch of problems with your code and if you have an Apple developer account, I highly suggest you head over to the 2012 WWDC Session Videos and look at the talk about Modern Objective C session. https://developer.apple.com/videos/wwdc/2012/
Looking at the code, your bad access could either be an already released object or an empty cell. To determine the cause, you need to enable NSZombies. To do so, click on your project name in the tool bar as shown here:
In this example, it's called PartialTransparentcy (spelling be damned). When you click that button, click the edit scheme button and you'lll see a list of options. You'll want to click enable Zombie objects. If you don't see it, press the Diagnostics tab.
Then run your code and it'll log the object that is giving your grief. Just remember that zombie objects are very expensive. If you submit an app with them enabled, you'll be automatically rejected.
The first thing I notice when I look at your cell initialization code is this:
TMTFamilyMemberCell *cell = (TMTFamilyMemberCell *)[tableView dequeueReusableCellWithIdentifier:nil];
You are sending in a nil identifier which negates that whole entire point of reusing cells. Instead, you are creating a new cell for each row instead of reusing older cells.
So uncomment this line:
static NSString *MyIdentifier = #"MyIdentifier";
and change the dequeue method to read like this:
TMTFamilyMemberCell *cell = (TMTFamilyMemberCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
If you are building your cell in interface builder, you'll need to also give the cell an identifier. This may be the cause of the crash.
Next ... your sqllite code should be encapsulated in another object. There's no reason for the view controller to know about it. Also, you should really investigate core data. It defaults to sqllite backing store. It's also a pretty awesome technology.
And finally, take a look at ARC. Let the compiler handle the grunt work of memory management so that you can focus on the user experience of your application.
Hope that helps and good luck!

Inserting Objects into NSMutableArray

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....

Cannot reorder an NSOrderedSet relationship in Core Data

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

Core Data Error "Fetch Request must have an entity"

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.