PDA

View Full Version : UITableView crashes when scrolling




Scott90
May 7, 2011, 02:00 PM
First of all: I have been doing research and this is a problem many, many developers have, but unfortunately I haven't gotten a solution out of it yet.

Situation: My view is a UITabBar with a UINavigationController and a UITableView. When I scroll the table, it crashes. The error I'm getting is index 9 beyond bounds of empty array, and the ninth table entry happens to be the first one that's not (completely) visible. Anyway, a code snippet says more than a thousand words:


// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

NSArray *buildingNames = [self.buildings allKeys];
NSArray *sortedBuildingNames = [buildingNames sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];

cell.textLabel.text = [sortedBuildingNames objectAtIndex:indexPath.row];

return cell;
}


I know it most likely has something to do with memory management, but I can't find it. I've tried retaining sortedBuildingNames because that seemed to be the solution for other people. Not in my case, though...

For some reason sortedBuildingNames becomes empty after the first eight rows are being displayed, but the array should be rebuilt for every cell (which is probably not very efficient?), so I don't understand how it could ever be empty.

Any experts who can shine a light on this?



chown33
May 7, 2011, 02:20 PM
Use the debugger.

Set a breakpoint on that method.

When the breakpoint is reached, confirm the following:
How many items does self.buildings contain?

Step through the method until right before cell.textLabel.text is assigned a value. At that point, confirm:
How many items does self.buildings contain? (Yes, do it again.)
Is buildingNames non-nil? How many items does it contain?
Is sortedBuildingNames non-nil? How many items does it contain?

The breakpoint will trigger every time the method is called. Confirm all the above for all 8 times it's called when setting up the initial display. When the breakpoint is triggered due to scrolling, confirm it all again.


If you don't know how to use the debugger, how to set breakpoints, how to inspect variables, how to determine the size of arrays, then now is a good time to learn. You won't get very far in serious programming until you know how to use debugging tools. Every problem will be a mystery to be puzzled out by sheer mental effort, instead of being an observable and testable phenomenon to be investigated.

Scott90
May 7, 2011, 02:43 PM
Of course, completely forgot about that tool... I haven't worked with it yet, but there's got to be a first time for everything. Thanks, if I find the solution I'll let you know what it was and if I don't I'll be back with my findings ;)

Scott90
May 7, 2011, 05:07 PM
Ok, so I got to a point where I figured out what line is causing the error:


NSArray *buildingNames = [self.buildings allKeys];


I added a breakpoint before that line, and for every time the method was called, I tested the value of buildings which returned the dictionary (retrieved from a plist in viewDidLoad) as it should be. So far so good.

Then, when I scrolled, the breakpoint stopped code execution, and I tested buildings again, and now it's empty, or non-existent. I think it's the first, because it doesn't give me a "no object at address" error.

This means allKeys: is being called on an empty dictionary, which shouldn't be a problem as it just returns an empty array, but then I get the index 9 beyond bounds of empty array error (and, apparently randomly also "[NSCFString allKeys] unrecognized selector sent to instance". The latter error doesn't make any sense, as at no point buildings is an NSString. The second one makes more sense, as I know buildings is empty. But how is it possible that variable is suddenly empty?

I simply create buildings this way:


NSString *path = [[NSBundle mainBundle] pathForResource:@"Buildings" ofType:@"plist"];
buildings = [NSDictionary dictionaryWithContentsOfFile:path];


In my .h, all I do is declaring an instance variable and creating a property for it (which I then synthesize in my .m).

So, long story short: I got it narrowed down to "buildings is suddenly empty", but the only place I ever change the value of buildings is in viewDidLoad (where it loads the .plist), and viewDidUnload (where it sets self.buildings to nil).

robbieduncan
May 7, 2011, 05:10 PM
Have you read the Memory Management Rules (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html%23//apple_ref/doc/uid/20000994-BAJHFBGH)?

If not do so now.

If you have they you should know that as you are not using alloc/init or a method with the word copy in it the dictionary returned by dictionaryWithContentsOfFile: will be autoreleased. So you'd expect the object to get automatically dealloced.

PhoneyDeveloper
May 7, 2011, 06:10 PM
You declared a @property but you don't use it in the code you show. It needs to be

self.buildings = whatever;

Scott90
May 7, 2011, 07:26 PM
Have you read the Memory Management Rules (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html%23//apple_ref/doc/uid/20000994-BAJHFBGH)?

If not do so now.

If you have they you should know that as you are not using alloc/init or a method with the word copy in it the dictionary returned by dictionaryWithContentsOfFile: will be autoreleased. So you'd expect the object to get automatically dealloced.
I did read that, and I know the rules for memory management, but I figured "well, I'm creating a property that retains the object, so it being autoreleased can't really be a problem." However, I didn't think of the fact that I actually wasn't using the property to set the instance variable. Thanks to your and PhoneyDeveloper's reply I came to that realization, and then quickly solved my problem.

I know to you this was probably another noob question, but if it's of any consolation, I not only have my code working now, but I also have a (much) better understanding of properties. Thanks for that, I appreciate it guys!

chown33
May 7, 2011, 07:33 PM
In my .h, all I do is declaring an instance variable and creating a property for it (which I then synthesize in my .m).

Don't describe your code, show it. Post the .h file.


FWIW, the symptoms you described are the classic ones for an under-retained variable: it sometimes "goes empty", or it sometimes "becomes something from a completely unrelated class".

In your case, it went from a dictionary to a string. This won't always happen, although weirdness always results. If you're lucky you get an exception like a "does not respond". If you're unlucky, the transubstantiated object DOES respond to the message, but the result is completely wrong.

A "Build and Analyze" pass on your project will often find bugs like this.

At runtime, another diagnostic is to run under Instruments with zombies enabled.
http://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/AboutTracing/AboutTracing.html

As a simple hygienic measure, it's a good idea to regularly run your code with zombies enabled, or perform "Build and Analyze". If you do things like this on a regular schedule, such as once a week (or once a day if you're rapidly expanding the code base), then problems like this are never left to fester and possibly corrupt other thing. Your mental recall of things recently changed is also better; this is invaluable because familiarity with What To Expect vs. What Actually Happens is crucial to debugging.