tags:

views:

94

answers:

3

I'm at a loss! It's one of those pesky bugs that happens only under specific conditions, yet I cannot link the conditions and the results directly.

My app has a paged UIScrollView where each page's view comes from a MyViewController, a subclass of UITableViewController. To minimize memory usage I unload those controllers that are not currently visible. Here is my "cleaning" method:

- (void) cleanViewControllers:(BOOL)all {

    if (all) { 
    // called if some major changes occurred and ALL controllers need to be cleared

        for (NSInteger i = 0; i < [viewControllers count]; i++)
            [viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
    }
    else if ([viewControllers count] > 2) {
    // called if only the nearest, no longer visible controller need to be cleared
        NSInteger i = pageControl.currentPage - 2;
        if (i > -1) [viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
        i = pageControl.currentPage + 2;
        if (i < [viewControllers count]) [viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
    }
}

It's this line that crashes the app:

viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];

viewControllers is an NSMutableArray containing objects of type MyViewController. MyViewController has no custom properties and its dealloc method contains nothing but a [super dealloc] call.

Here is what the debugger shows: alt text

The thing is that this does not happen every time the controller is cleared, but only sometimes. Specifically, after certain changes trigger a complete cleaning and re-drawing of the ScrollView, it displays the current page (call it X) fine, but as soon as I scroll far enough to cause cleaning of X, this crash happens. It's driving me nuts!

Another thing, this does not happen in the 4.0 Simulator, nor on an iPad, but happens very consistently on a 1st gen iPod touch running 3.1.3.

+2  A: 

Something is being released but a pointer is still dangling. Set NSZombieEnabled to YES and run it again. Here's how:

  • Project -> Edit Active Executable
  • Select the "Arguments" tab
  • Add to "Variables to be set in the environment"
    • Name: NSZombieEnabled
    • Value: YES

Run the app again. At some point it'll tell you you're accessing an already released object. From that you'll need to figure out who's not retaining or who's over-releasing the object.

Happy Zombie Hunting.

John Franklin
Zombie Hunting? Sounds like fun! Thanks for the suggestion.
SaltyNuts
A: 

Instead of replacing the elements in the array you can simply call removeObjectAtIndex: or removeAllObjects: to make sure there is nothing holding reference where it shouldn't.

RupertP
Sounds irrelevant.
tc.
There is no need to insert null values into the array when the objects could be removed instead. Won't affect retainCount but make code more readable and easier to debug (when outputting the content of the array to the console)
RupertP
same thing happens if i remove the objects altogether instead of replacing with null. the reason i use null is because that way i keep track of what's loaded and displayed.
SaltyNuts
You're missing the point. a[i] contains either the view controller for page i (if it's loaded) or [NSNull null] (if it's not loaded).
tc.
A: 

View controllers will automatically unload their views on a memory warning by default, so there's no reason to unload the controllers themselves unless they include significant overhead.

Is the view controller's view still in the view hierarchy? You can check with something like [viewController isViewLoaded] && viewController.view.superview. If so, it's probably not safe to remove the view controller.

(Note the isViewLoaded check, since UIViewController.view will load the view if it's not already loaded.)

tc.
unfortunately, especially on older hardware such as 1st gen iPod touch, the extra views do slow things down considerably.
SaltyNuts
Then use `[self.view removeFromSuperview]` first?
tc.