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:
- Do one of those complicated navigations
- Navigate back one or two times depending on navigation kind
- 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:
- 0x0027a9 mysighandler()
- 0x3293d82b _sigtramp()
- 0x31c59065 -[UIApplication sendAction:to:from:forEvent:]
- 0x31c59005 -[UIApplication sendAction:toTarget:fromSender:forEvent:]
- 0x31c58fd7 -[UIControl sendAction:to:forEvent:]
- 0x31c58d31 -[UIControl _sendActionsForEvents:withEvent:]
- 0x31c59645 -[UIControl touchesEnded:withEvent:]
- 0x31c5865d -[UIWindow _sendTouchesForEvent:]
- 0x31c58039 -[UIWindow sendEvent:]
- 0x31c5492f -[UIApplication sendEvent:]
- 0x31c543a7 _UIApplicationHandleEvent()
- 0x3352c9ed PurpleEventCallback()
- 0x3358ac2d CFRunLoopRunSpecific()
- 0x3358a35d CFRunLoopRunInMode()
- 0x3352bb33 GSEventRunModal()
- 0x3352bbdf GSEventRun()
- 0x31c1976f -[UIApplication _run]
- 0x31c18473 UIApplicationMain()
- 0x00214d main()
- 0x0020c4 start()
Another one:
- 0x002945 mysighandler()
- 0x3293d82b _sigtramp()
- 0x31c5ead3 -[UIScrollView _updatePanWithStartDelta:event:gesture:ignoringDirectionalScroll:]
- 0x31c5e435 -[UIScrollView handlePan:]
- 0x31d14651 -[UITableView handlePan:]
- 0x33590da7 -[Protocol performSelector:withObject:]
- 0x31c428b5 -[UIGestureRecognizer _updateGestureWithEvent:]
- 0x31c427a9 -[UIGestureRecognizer _updateGestureStateWithEvent:afterDelay:]
- 0x31c583d5 -[UIWindow _sendGesturesForEvent:]
- 0x31c5802b -[UIWindow sendEvent:]
- 0x31c5492f -[UIApplication sendEvent:]
- 0x31c543a7 _UIApplicationHandleEvent()
- 0x3352c9ed PurpleEventCallback()
- 0x3358ac2d CFRunLoopRunSpecific()
- 0x3358a35d CFRunLoopRunInMode()
- 0x3352bb33 GSEventRunModal()
- 0x3352bbdf GSEventRun()
- 0x31c1976f -[UIApplication _run]
- 0x31c18473 UIApplicationMain()
- 0x0022e9 main()
- 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).