4
votes

I'm having a strange issue and I don't know if it is my bug (most probably) or a bug in UINavigationController.

I use UINavigationController in my application. In some cases I need complicated navigations like "pop 2 screens and push new one". Currently I do it by getting current navigationController.viewControllers, modifying the collection, and calling [navigationController setViewControllers:newStack animated:YES].

It occurs that this makes my application crash very often. Usually crashes are SIGBUS or SIGSEGV. Steps to reproduce looks like following:

  1. Do one of those complicated navigations
  2. Navigate back one or two times depending on navigation kind
  3. Crash!

Interesting things are that:

  • If navigation was simply "several screens back" then next back would crash. If navigation was "several screens back and push new screen" then second back would crash. Moreover, in such case navigations like back, forward, back, back usually doesn't crash application
  • The most strange: if I do [navigationController popToRootViewControllerAnimated:NO] before updating stack, application doesn't crash or at least crashes much rarer (I wasn't able to reproduce crash).

Example of crash stack trace caught by my signal handler:

  1. 0x0027a9 mysighandler()
  2. 0x3293d82b _sigtramp()
  3. 0x31c59065 -[UIApplication sendAction:to:from:forEvent:]
  4. 0x31c59005 -[UIApplication sendAction:toTarget:fromSender:forEvent:]
  5. 0x31c58fd7 -[UIControl sendAction:to:forEvent:]
  6. 0x31c58d31 -[UIControl _sendActionsForEvents:withEvent:]
  7. 0x31c59645 -[UIControl touchesEnded:withEvent:]
  8. 0x31c5865d -[UIWindow _sendTouchesForEvent:]
  9. 0x31c58039 -[UIWindow sendEvent:]
  10. 0x31c5492f -[UIApplication sendEvent:]
  11. 0x31c543a7 _UIApplicationHandleEvent()
  12. 0x3352c9ed PurpleEventCallback()
  13. 0x3358ac2d CFRunLoopRunSpecific()
  14. 0x3358a35d CFRunLoopRunInMode()
  15. 0x3352bb33 GSEventRunModal()
  16. 0x3352bbdf GSEventRun()
  17. 0x31c1976f -[UIApplication _run]
  18. 0x31c18473 UIApplicationMain()
  19. 0x00214d main()
  20. 0x0020c4 start()

Another one:

  1. 0x002945 mysighandler()
  2. 0x3293d82b _sigtramp()
  3. 0x31c5ead3 -[UIScrollView _updatePanWithStartDelta:event:gesture:ignoringDirectionalScroll:]
  4. 0x31c5e435 -[UIScrollView handlePan:]
  5. 0x31d14651 -[UITableView handlePan:]
  6. 0x33590da7 -[Protocol performSelector:withObject:]
  7. 0x31c428b5 -[UIGestureRecognizer _updateGestureWithEvent:]
  8. 0x31c427a9 -[UIGestureRecognizer _updateGestureStateWithEvent:afterDelay:]
  9. 0x31c583d5 -[UIWindow _sendGesturesForEvent:]
  10. 0x31c5802b -[UIWindow sendEvent:]
  11. 0x31c5492f -[UIApplication sendEvent:]
  12. 0x31c543a7 _UIApplicationHandleEvent()
  13. 0x3352c9ed PurpleEventCallback()
  14. 0x3358ac2d CFRunLoopRunSpecific()
  15. 0x3358a35d CFRunLoopRunInMode()
  16. 0x3352bb33 GSEventRunModal()
  17. 0x3352bbdf GSEventRun()
  18. 0x31c1976f -[UIApplication _run]
  19. 0x31c18473 UIApplicationMain()
  20. 0x0022e9 main()
  21. 0x002260 start()

Example of "complicated" navigation implementation:

@implementation UINavigationController(MyCategory)
- (void)popViewControllers:(NSInteger)count {
    NSArray* oldList = self.viewControllers;
    NSMutableArray* newList = [NSMutableArray arrayWithArray:oldList];
    if(count > [oldList count]) {
        CLogError(@"Poping %d screens when there is only %d", count, [oldList count]);
        count = [oldList count] - 1;
    }
    for(int i = 0; i<count; i++) {
        [newList removeLastObject];
    }
    [self setViewControllers:newList animated:YES];
}
@end

Does anybody now, what I might be doing wrong? I just run out of ideas.

Addition:

I did run my application using NSZombieEnabled and MallocStackLogging to find out what object fails. However it didn't give my reasonable results. For stack trace #1 it fails at step 3 (-[UIApplication sendAction:to:from:forEvent:]) and zombie object is -[UIBarButtonItem performSelector:withObject:withObject:]: message sent to deallocated instance 0xa5f5f90. This is right navigation bar button of the screen that application navigates 2 screens back from (and remember, this 2-screens-back navigation works, only next "usual" back navigation fails). But I don't do anything with that button. Corresponding code in ViewControler's initWithSomething:(Something*)something is:

UIBarButtonItem* doneItem = [[UIBarButtonItem alloc] initWithTitle:@"Complete"
                                                             style:UIBarButtonItemStyleDone 
                                                             target:self action:@selector(onDone)];
self.navigationItem.rightBarButtonItem = doneItem;
[doneItem release]; 

The only special thing about this button is that onDone selector does that 2-screens-back navigation, but I don't think that this really matters. So I believe there is something wrong at higher level object (probably View Controller or UINavigationController?). But what is wrong?

Addition October, 4 2011:

As people are still sometimes search for this question, here is some code. My current approach to this issue is to use custom subclass of UINavigationController instead of it with following hack (no guarantees that this works or is still necessary):

@interface CustomUINavigationController : UINavigationController {
}

@end


@implementation CustomUINavigationController
- (void)setViewControllers:(NSArray*)newStack animated:(BOOL)animated { 
    // HACK HACK
    // Somehow everything fails if I don't clean stack before putting new
    // But on iOS4 popToRootViewControllerAnimated might call setViewControllers:animated
    // let's avoid call stack overflow
    static int stackCount = 0;
    if(!stackCount++) {
        if([self.viewControllers count] != 1) {
            [self popToRootViewControllerAnimated:NO];
        }
        else {
            UIViewController* tmpVc = [[[UIViewController alloc] init] autorelease];
            NSArray* tmpStack = [NSArray arrayWithObject:tmpVc];
            [super setViewControllers:tmpStack animated:NO];                
        }
    }
    [super setViewControllers:newStack animated:animated];
    stackCount--;
}
@end

Another important thing: you'd better don't start animated navigation while previous animated navigation is still in progress (i.e. at least till viewWillAppear: is called).

3
I think that this problem is similar to: stackoverflow.com/a/12076199/980903lyzkov

3 Answers

2
votes

I think it would be worth running it through Instruments in Zombies mode. This is almost certainly a memory issue, with something accessing an object that's already released.

1
votes

Had a similar problem. It turns out that at least in some circumstances the action of a rightBarButtonItem is called when a view controller is popped. My ugly fix for this is to remove the offending item when I push the next view controller on to the stack. Then I check in viewWillAppear if rightBarButton is nil and recreate the button if neccessary.

However, recreating the button will have it hover at 0, 0 for a fraction of a second before popping into place at the right side of the navigation bar. A slightly more elegant fix would be to set the action of the button to the default value of 'NULL'. This solves the first problem but will also break the backBarButtonItem.

So to sum up, I'm still looking for a proper fix for this. I'm about a day's work from contacting Apple with this -- might even be a bug, come to think of it...

1
votes

For the second stack trace, I had the same problem. I was releasing the view controller which contained the scrollview.

[mainScrollView addSubview:rubriqueController.view];        
[rubriqueController release]; // Comment this line

Something like this.

Hope it helps even 9 months after.