views:

1030

answers:

5

Hi,

My UITableView has up to 50 rows in each section, populated by a plist dictionary which contains arrays (sections) which contains arrays (row objects) which contains two strings (row title / filename and file extension).

Select row 1 - 25 (item 0-24) and everything behaves normally. But select a row greater than 26 (item 25) and the app crashes. I'm a novice at all this and I tried researching for the answer but I'm at a loss for how to research this. Can tables only have 25 rows per section?

Any ideas? Thank you!

Jon

- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];
    NSMutableString *key = [categories objectAtIndex:section];
    NSMutableArray *sound = [categoriesSounds objectForKey:key];
    NSMutableString *soundName = [[sound objectAtIndex: row] objectAtIndex: 0];
    NSMutableString *soundOfType = [[sound objectAtIndex: row] objectAtIndex: 1];


    if (leftSwitch.on == YES) {
        showLeft.text = soundName;
        left    = [[NSBundle mainBundle] pathForResource:(@"%@", soundName) ofType:(@"%@", soundOfType)];
        AudioServicesCreateSystemSoundID((CFURLRef)[NSURL
                                                    fileURLWithPath:left], &soundNegZ); 
        AudioServicesPlaySystemSound (soundNegZ); 

        if (indexPath != leftOldIndexPath) {
            UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
            newCell.accessoryType = UITableViewCellAccessoryCheckmark;
            UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:leftOldIndexPath];
            oldCell.accessoryType = UITableViewCellAccessoryNone;
            leftOldIndexPath = indexPath;
        }
        [tableView deselectRowAtIndexPath:indexPath animated:YES];


    }

    if (downSwitch.on == YES) {
        showDown.text = soundName;
        down    = [[NSBundle mainBundle] pathForResource:(@"%@", soundName) ofType:(@"%@", soundOfType)];
        AudioServicesCreateSystemSoundID((CFURLRef)[NSURL
                                                    fileURLWithPath:down], &soundNegX); 
        AudioServicesPlaySystemSound (soundNegX);

        if (indexPath != downOldIndexPath) {
            UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
            newCell.accessoryType = UITableViewCellAccessoryCheckmark;
            UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:downOldIndexPath];
            oldCell.accessoryType = UITableViewCellAccessoryNone;
            downOldIndexPath = indexPath;
        }
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }

    if (rightSwitch.on == YES) {
        showRight.text = soundName;
        right    = [[NSBundle mainBundle] pathForResource:(@"%@", soundName) ofType:(@"%@", soundOfType)];
        AudioServicesCreateSystemSoundID((CFURLRef)[NSURL
                                                    fileURLWithPath:right], &soundPosX);    
        AudioServicesPlaySystemSound (soundPosX);

        if (indexPath != rightOldIndexPath) {
            UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
            newCell.accessoryType = UITableViewCellAccessoryCheckmark;
            UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:rightOldIndexPath];
            oldCell.accessoryType = UITableViewCellAccessoryNone;
            rightOldIndexPath = indexPath;
        }
        [tableView deselectRowAtIndexPath:indexPath animated:YES];

    }



}

For "EXC_BAD_ACCESS" in the console, this is the debugger output (error trace is marked with asterisks):

0x91a6cec0  <+0000>  mov    0x8(%esp),%ecx
0x91a6cec4  <+0004>  mov    0x4(%esp),%eax
0x91a6cec8  <+0008>  cmp    $0xfffeb010,%ecx
0x91a6cece  <+0014>  je     0x91a6cf35 <objc_msgSend+117>
0x91a6ced0  <+0016>  test   %eax,%eax
0x91a6ced2  <+0018>  je     0x91a6cf1a <objc_msgSend+90>
0x91a6ced4  <+0020>  mov    (%eax),%edx
0x91a6ced6  <+0022>  push   %edi
**0x91a6ced7  <+0023>  mov    0x20(%edx),%edi** 
0x91a6ceda  <+0026>  push   %esi
0x91a6cedb  <+0027>  mov    (%edi),%esi
0x91a6cedd  <+0029>  mov    %ecx,%edx
0x91a6cedf  <+0031>  shr    $0x2,%edx
0x91a6cee2  <+0034>  and    %esi,%edx
0x91a6cee4  <+0036>  mov    0x8(%edi,%edx,4),%eax
0x91a6cee8  <+0040>  test   %eax,%eax
0x91a6ceea  <+0042>  je     0x91a6cef5 <objc_msgSend+53>
0x91a6ceec  <+0044>  cmp    (%eax),%ecx
0x91a6ceee  <+0046>  je     0x91a6cf00 <objc_msgSend+64>
0x91a6cef0  <+0048>  add    $0x1,%edx
0x91a6cef3  <+0051>  jmp    0x91a6cee2 <objc_msgSend+34>
0x91a6cef5  <+0053>  pop    %esi
0x91a6cef6  <+0054>  pop    %edi
0x91a6cef7  <+0055>  mov    0x4(%esp),%eax
0x91a6cefb  <+0059>  mov    (%eax),%eax
0x91a6cefd  <+0061>  jmp    0x91a6cf09 <objc_msgSend+73>
0x91a6ceff  <+0063>  nop    
0x91a6cf00  <+0064>  mov    0x8(%eax),%eax
0x91a6cf03  <+0067>  pop    %esi
0x91a6cf04  <+0068>  pop    %edi
0x91a6cf05  <+0069>  xor    %edx,%edx
0x91a6cf07  <+0071>  jmp    *%eax
0x91a6cf09  <+0073>  sub    $0x4,%esp
0x91a6cf0c  <+0076>  push   %ecx
0x91a6cf0d  <+0077>  push   %eax
0x91a6cf0e  <+0078>  call   0x91a6d33f <_class_lookupMethodAndLoadCache>
0x91a6cf13  <+0083>  add    $0xc,%esp
0x91a6cf16  <+0086>  xor    %edx,%edx
0x91a6cf18  <+0088>  jmp    *%eax
0x91a6cf1a  <+0090>  call   0x91a6cf1f <objc_msgSend+95>
0x91a6cf1f  <+0095>  pop    %edx
0x91a6cf20  <+0096>  mov    0xe79d961(%edx),%eax
0x91a6cf26  <+0102>  test   %eax,%eax
0x91a6cf28  <+0104>  je     0x91a6cf30 <objc_msgSend+112>
0x91a6cf2a  <+0106>  mov    %eax,0x4(%esp)
0x91a6cf2e  <+0110>  jmp    0x91a6ced4 <objc_msgSend+20>
0x91a6cf30  <+0112>  mov    $0x0,%edx
0x91a6cf35  <+0117>  ret    
0x91a6cf36  <+0118>  nopw   %cs:0x0(%eax,%eax,1)

My cellForRowAtIndexPath method:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];
    NSString *key = [categories objectAtIndex:section];
    NSArray *nameSection = [categoriesSounds objectForKey:key];
    static NSString *SectionsTableIdentifier = @"SectionsTableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
                             SectionsTableIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc]
                 initWithStyle:UITableViewCellStyleDefault
                 reuseIdentifier:SectionsTableIdentifier] autorelease];
    }
    cell.textLabel.text = [[nameSection objectAtIndex:row] objectAtIndex: 0];

    if (leftSwitch.on == YES){
        NSUInteger leftRow = [leftOldIndexPath row];
        NSUInteger leftSection = [leftOldIndexPath section];
        cell.accessoryType = (row == leftRow && section == leftSection && leftOldIndexPath !=nil) ?
        UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
        }
    if (downSwitch.on == YES){
        NSUInteger downRow = [downOldIndexPath row];
        NSUInteger downSection = [downOldIndexPath section];
        cell.accessoryType = (row == downRow && section == downSection && downOldIndexPath !=nil) ?
        UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
    }
    if (rightSwitch.on == YES){
        NSUInteger rightRow = [rightOldIndexPath row];
        NSUInteger rightSection = [rightOldIndexPath section];
        cell.accessoryType = (row == rightRow && section == rightSection && rightOldIndexPath !=nil) ?
        UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
    }

    return cell;
}
A: 

Hi Jon,

When your app crashes do you see any error messages logged in the debugger console? (If it's not viewable whilst you debug your application select the Run menu -> Console menu option in XCode). You should see some log entries in here that will help to point you in the right direction.

EddieCatflap
Hi Eddie,Thanks for taking the time to respond - I didn't even know about the debugger and console... I received an "EXC_BAD_ACCESS" message, which I learned is pretty generic and usually means there's not enough memory allocated. I pasted the console output in the ticket above but realize now there is output for lots of threads, not just that one. Following the trace to the end, the error seems to point to "int retVal = UIApplicationMain(argc, argv, nil, nil);" Thanks again.
Jonathan Cohen
No problem, also take a look at http://www.tomwhitson.co.uk/blog/2009/04/debugging-with-nszombiesenabled/. It details setting the NSZombieEnabled environment variable, which is a great help when tracing EXC_BAD_ACCESS errors. Follow the instructions in that link and you should find that your app will no longer die ungracefully but that you'll get log messages in the console advising you which object(s) you are 'over-releasing', if you like.Once you've tracked down and fixed the problem please remember to set this environment variable to NO though straight-away, it is purely a debugging aid.
EddieCatflap
Thanks Eddie, NSZombieEnabled pointed me in the right direction and I was able to fix the problem. Turned out my index path retain count needed to be increased by 1 in cellForRowAtIndexPath.
Jonathan Cohen
A: 

I have 101 rows in my current project. As far as i know there isn't a limit.

As for the crash, can you give us more info? Like a stack trace? Console output?

Combat
Hi Combat - I updated the ticket to show my console output and debugger info. I'm not sure where to start... but I found a few tutorials on the debugger. Thanks for responding.
Jonathan Cohen
+2  A: 

From analysing your code, I can see a few places where you can be failing.

First of all:

NSMutableString *soundName = [[sound objectAtIndex: row] objectAtIndex: 0];
NSMutableString *soundOfType = [[sound objectAtIndex: row] objectAtIndex: 1];

Do you have more than 25 elements in the sound array?

Second of all:

if (indexPath != downOldIndexPath) {

What is downOldIndexPath? Are you sure that if you pass it to this method

UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:downOldIndexPath];

it is a correct index path?

Adam Woś
Hi Adam - thanks your help. Yes, some sound arrays have more than 25 elements, depending on the section in the table. Everything works as expected if element 25 or less is selected.leftOldIndexPath, downOldIndexPath, and rightOldIndexPath are used to persist the selected index path while one of three corresponding UISwitches (left, down, right) is set to ON. I'm showing a checkmark accessory icon for the sound selected while the left switch is set to on and hiding it while the down and right switches are set to on. It's definitely a correct index path; it works when row <26. Thanks again.
Jonathan Cohen
A: 

If you were accessing more elements than the array held, you would get an out of bounds exception - not a EX_BAD_ACCESS crash.

That means you are trying to access memory you have released. There are two possibilities:

1) The item you are pulling out of the array, has been released. This is pretty unlikely as you would have had to do extra releases to get rid of something held in an array.

2) (most likely) You have released one of the arrays you are trying to get data from - or more accurately, you get the array from somewhere and then forget to retain it (since most things given to you by other methods are auto-released).

Kendall Helmstetter Gelner
Thanks for the direction Kendall -- I'll read up on memory management.
Jonathan Cohen
A: 

Hi Jonathan,

I'm having this same problem, I see you've solved it by the following:

Thanks Eddie, NSZombieEnabled pointed me in the right direction and I was able to fix the problem. Turned out my index path retain count needed to be increased by 1 in cellForRowAtIndexPath. – Jonathan Cohen Jan 25 at 18:00

Sorry I'm a bit new to iPhone programming, what does this mean exactly?

I've tried a few things and they all seem to work I'm just wondering which is the correct way!?

I have tried this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CheckMarkCellIdentifier = @"CheckMarkCellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CheckMarkCellIdentifier];

    if (cell == nil) {
        cell = [[[UITableViewCell alloc]
                 initWithStyle:UITableViewCellStyleSubtitle
                 reuseIdentifier:CheckMarkCellIdentifier] autorelease];
    }

    // need to retain to stop this crashing with rows > ~26
    [indexPath retain];

    NSUInteger row = [indexPath row];
    NSUInteger oldRow = [lastIndexPath row];

    // Print the text
    cell.textLabel.text = [itemTextList objectAtIndex:row];

    // Print the detail text
    cell.detailTextLabel.text = [itemDetailTextList objectAtIndex:row];

    cell.accessoryType = (row == oldRow && lastIndexPath != nil) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;

    return cell;
}

Also (separately) I've tried both of these:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger newRow = [indexPath row];
    NSUInteger oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1;

    if (newRow != oldRow) {
        UITableViewCell *newCell = [tableView cellForRowAtIndexPath: indexPath];
        newCell.accessoryType = UITableViewCellAccessoryCheckmark;

        UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: lastIndexPath];
        oldCell.accessoryType = UITableViewCellAccessoryNone;

        // retain indexPath to use as last index path       
        lastIndexPath = [indexPath retain];
    }

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

and

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger newRow = [indexPath row];
    NSUInteger oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1;

    if (newRow != oldRow) {
        UITableViewCell *newCell = [tableView cellForRowAtIndexPath: indexPath];
        newCell.accessoryType = UITableViewCellAccessoryCheckmark;

        UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: lastIndexPath];
        oldCell.accessoryType = UITableViewCellAccessoryNone;

        // get a copy to use as last index path     
        lastIndexPath = [indexPath copy];
    }

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

Now I'm wavering towards the last [indexPath copy], is this the correct way to do it from a memory management point of view? Or does it not really matter whether I retain or copy it?

Thanks,

Matt.

Matt Caves
Fixed this by changing to:self.lastIndexPath = indexPath;This sorts out the retain etc. automatically (and properly), a little gotcha for newbies! :-)
Matt Caves