views:

988

answers:

2

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?

+1  A: 

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.

buggles
Sorry that I didn't mention this in my original text. I tried Zombie mode. I added details to the question
iPhone beginner
A: 

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...

Royen
After som additional messing around, I've solved the problems introduced by the 'fix' by using setRightBarButtonItem:animated: instead of setting the rightBarButtonItem directly. Still looking for a way to solve the original issue though...
Royen
As I said, it seems that I have a workaround for my issue. I use my custom sub-class of `UINavigationController` with overridden `setViewControllers:animated:` that calls `[self popToRootViewControllerAnimated:NO]` before calling `super`. But I don't like it because I don't understand why it works and why my code fails. It might be a sign of some deeper issue in my code.
iPhone beginner
Have you reported this bug yet? I'm still having this problem in iOS 4.
an0