EXC_BAD_ACCESS error when removing object from NSMutableDictionary - objective-c

I have a NSMutableDictionary as the datasource for my UITableView. I am trying to implement the delete mode and having an issue.
I am logging the key I am trying to remove as well as the object that it corresponds to as this issue seems like it might be related to my trying to access unallocated memory or something. Here is my implementation of tableView:commitEditionStyle:forRowAtIndexPath:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source.
NSArray * keys = [userList allKeys];
NSNumber *keyToRemove = [keys objectAtIndex:indexPath.row];
NSLog(#"Key to remove: %#",keyToRemove);
NSLog(#"Object at key: %#",[userList objectForKey:keyToRemove]);
[userList removeObjectForKey:keyToRemove];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[keys release];
[keyToRemove release];
}
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.
}
}
This method runs and then I get the error. The two NSLog statements output the correct key and it's corresponding value.
Can someone tell me what I'm doing wrong?
Thanks

You don't own keys or keysToRemove, so you should not be releasing them. I strongly suggest reading up on the Cocoa memory management rules.

Here's your problem:
[keys release];
[keyToRemove release];
You are releasing keys and keyToRemove, even though you never allocated it, retained it, or copied it, so it's reference count is decreasing more than it should.
As a general rule, you should only release an object if you called alloc, retain (not init, sorry) or copy on it, I recommend you read on reference counting here: Practical Memory Management

Related

Application crashing on numberOfRowsSection

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [array count]; // <--- Crashes here
}
I'm not sure why it is crashing considering the array is a simple NSArray.
Your "array" must not be an NSArray. Can you provide some context for where array is defined? Are you using ARC?
A simple test would be to put the following line before that return statement:
NSLog(#"array = %#; array class = %#", array, array.class)
Paste the output from that and I can help you further! My guess currently is that array is being deallocated, and there's some other (or garbage) property in that memory. I wouldn't be surprised if you saw that array.class was an NSNumber...

Adding object to a column in a cell-based NSTableView?

I'm having trouble with Columns in NSTableview. I want to add an item to a specific column in an instance of NSTableView. (Note that data is a pointer to an instance of NSMutableArray, which is a property of AppDelegate, which is the dataSource)
-(IBAction)addTask:(id)sender
{
[self.data addObject:#""];
[self tableView:_tableView setObjectValue:#"Hello World" forTableColumn: self.column row:0];
[self.tableView reloadData];
}
A tutorial I went through showed me how to edit a single-column NSTableView. However, using the same implementation of the NSTableViewDataSource methods causes tableView to populate both columns. The pertinent implementation from the tutorial:
-(void)tableView:(NSTableView *)tableView
setObjectValue:(id)object
forTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row
{
[self.data replaceObjectAtIndex:row withObject:object];
}
I reason part of the issue is that the above method doesn't do anything with the tableColumn parameter, but I'm still learning and have no idea how to proceed. (It's especially hard to find help because many modern tutorials involve view-based NSTableViews, and well I know that is best practice, I don't want to run away from this.) I hope my explanation was clear enough, and any help would be much appreciated.
The reason you are seeing the data populated in multiple columns is you are not differentiating what values go in what columns.
You should think of each item in your array as a row in your table view. If you wish to display different values in columns of that row you need a way to store those different values in the data source object. This can be done by using a concrete model object class that has multiple properties defined or by using a NSDictionary with different keys with their corresponding values.
For adding a row:
-(IBAction)addTask:(id)sender
{
NSMutableDictionary *newRow = [NSMutableDictionary dictionary];
[newRow setObject:#"Hello World" forKey:#"salutation"];
[self.data addObject:newRow];
[self.tableView reloadData];
}
So for the display code:
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSDictionary *row = [self.data objectAtIndex:rowIndex];
NSString *columnIdentifier = [aTableColumn identifier];
return [row objectForKey:columnIdentifier];
}
You'll notice we are using the column identifier here. Normally you set this value in Interface Builder on the table column. In the case above the identifier needs to be the same as the key in the dictionary for which you wish to display information.
Finally allowing the user to set a value of a column in the table view:
-(void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger) rowIndex
{
NSDictionary *row = [self.data objectAtIndex:rowIndex];
NSString *columnIdentifier = [aTableColumn identifier];
[row setValue:anObject forKey:columnIdentifier];
}

Issues using NSIndexPath as key in NSMutableDictionary?

Is there any particular reason why attempting to store and retrieve a value in an NSMutableDictionary using an NSIndexPath as a key might fail?
I originally attempted to do this in order to store an NSMutableDictionary of UITableViewCell heights (self.cellHeights) for a UITableView. Each time you tapped a UITableViewCell, that cell would either expand or contract between two different heights based on the value stored in the NSMutableDictionary for that particular indexPath:
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber *heightNSNumber = [self.cellHeights objectForKey:indexPath];
if (!heightNSNumber)
{
heightNSNumber = [NSNumber numberWithFloat:100.0];
[self.cellHeights setObject:heightNSNumber forKey:indexPath];
}
return [heightNSNumber floatValue];
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSNumber *heightNSNumber = [self.cellHeights objectForKey:indexPath];
if (!heightNSNumber)
{
heightNSNumber = [NSNumber numberWithFloat:100.0];
[self.cellHeights setObject:heightNSNumber forKey:indexPath];
}
if ([heightNSNumber floatValue] == 100.0)
{
[self.cellHeights setObject:[NSNumber numberWithFloat:50.0]
forKey:indexPath];
} else {
[self.cellHeights setObject:[NSNumber numberWithFloat:100.0]
forKey:indexPath];
}
[tableView beginUpdates];
[tableView endUpdates];
}
For reasons unknown to me, getting the cell height within tableView:didSelectRowAtIndexPath: via [self.cellHeights objectForKey:indexPath] works just fine. However, trying to get the cell height within tableView:heightForRowAtIndexPath: via [self.cellHeights objectForKey:indexPath] always returns nil because it seems that the indexPath used to store the height doesn't match the indexPath being used to fetch the cell height, even though they have the same values for indexPath.section and indexPath.row. Because of this, a new object for the "same" index path is added to self.cellHeights (as evident since self.cellHeights.count increases thereafter).
This does not happen when you store the cell heights in the NSMutableDictionary using the row ([NSNumber numberWithInteger:indexPath.row]) as the key...so that's what I'm doing for now, but I'd like to understand why indexPath isn't working as the key.
Although I'm late in the discussion, here's a quick and simple solution that will allow you to use NSIndexPath instances as dictionary keys.
Just recreate the indexPath by adding the following line:
indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
VoilĂ . tableView:heightForRowAtIndexPath: uses NSMutableIndexPath instances internally (as you would see with a breakpoint). Somehow those instances seem uncooperative with NSIndexPath when calculating hash keys.
By converting it back to an NSIndexPath, then everything works.
#Jean's answer seems acceptable, but this question has been answered in more detail here. In short, UITableView sometimes uses instances of NSMutableIndexPath instead of NSIndexPath and instances of these two classes are never equal because [NSMutableIndexPath class] != [NSIndexPath class]. The workaround is to always generate a key NSIndexPath for anything that relies on isEqual or hash, such as looking up dictionary keys:
- (NSIndexPath *)keyForIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath class] == [NSIndexPath class]) {
return indexPath;
}
return [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
}
There are several things that must be implemented for an object to work reliably as a key for NSDictionary, namely isEqual:, hash and Copyable protocol.
I am not very sure that NSIndexPath was ever intented to work as a key for dictionaries (because it was made to be an index for arrays).
My guess is that hash is not implemented correctly for different instances of the class. Also note that some of the table delegate methods are called with NSIndexPath and some with NSMutableIndexPath. That's probably making the difference.

Adding data to a tableView using a NSMutableArray

I'm having a problem adding an item to my tableView.
I used to initialize an empty tableView at the start of my App and get it filled with scanned items every time the tableView reappears and there is an item in my variable.
Initialization of the tableView:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:nil];
self.listArray = array;
TableView Data Source:
- (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 [self.listArray count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if(section == 0)
return #"Eingescannte Artikel:";
else
return nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"testCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
NSUInteger row = [indexPath row];
cell.textLabel.text = [listArray objectAtIndex:row];//[NSString stringWithFormat:#"Das ist Zeile %i", indexPath.row];
return cell;
}
(Not the whole thing but the ones I changed)
As you may have seen I use an NSMutableArray to add items to my tableView.
So if an item ist scanned I'm adding it to my array like this:
[listArray insertObject:sharedGS.strEAN atIndex:0]; //using a shared Instance where I implemented my variable.
I also tried to use an variable to extend my Index every time a new Item is added, but it won't work both ways.
I'm quite new to programming so an not-too-hard-to-understand-answer would be quite nice ;)
If there's any information missing, feel free to ask.
/edit: Trying to specify my question: The data from the variable is written in a TableViewCell, but if I scan another one the other one is just being replaced. Not sure if it's a problem with my array or my tableView...
/edit No.2: Found out(thanks to fzwo) that my array isn't working correctly. It just doesn't grow by an addObject: or insertObject:atIndex: command. But I just don't get why... :(
All I'm doing: [listArray addObject:sharedGS.strEAN]; not that much space for errors in one simple line. Maybe I'm just too stupid to recognize what I'm doing wrong:D
You state that your problem is "adding an item to my tableView" , since you are adding the object to your array i am guessing the problem is that you are not reloading the table or that it is missing the dataSource binding.
You have not actually asked any question (even if you added info to "specify your question") so a wild guess, after
[listArray insertObject:sharedGS.strEAN atIndex:0];
put
[yourTableView reloadData];
Are you intentionally adding new items to the top of the table ? otherwise you could do
[listArray addObject:sharedGS.strEAN]; to add new items to the bottom
Otherwise it's worth noting that you are misusing dequeueReusableCellWithIdentifier, look at the example below for proper usage:
// Try to retrieve from the table view a now-unused cell with the given identifier
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
// If no cell is available, create a new one using the given identifier
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}

Do i need to release NSMutableDictionary in this case?

Do I need to release dictCellCollectionIndividual in this case?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
... ...
... ...
... ...
// Local declaration of dictCellCollectionIndividual
NSMutableDictionary *dictCellCollectionIndividual;
// Copy the specific dictionary from dictCellCollection to dictCellCollectionIndividual. dict CellCollection is declared elsewhere.
dictCellCollectionIndividual = [dictCellCollection objectForKey:[NSString stringWithFormat:#"%#", [arrayCellCollectionOrder objectAtIndex:indexPath.row]]];
... ...
... ...
... ...
// Do I need to release?
[dictCellCollectionIndividual release];
return cell;
}
Doesn't using objectForKey increase the retain count? Don't I have to release it?
Thanks in advance.
No, it simply returns a pointer to the object held within the dictionary; if you plan to keep the object around for any period of time, you need to take ownership of it, so that the object will remain valid if NSDictionary is deallocated in the meantime. In this case, you'll need to make sure to either release or autorelease the object when you're done with it. If you're planning to use it only as a temporary value (say, as an argument to an NSLog call), it's probably unnecessary to retain the object, and therefore unnecessary to release it.