UINavigationController setViewControllers causes the application to crash

I have a strange problem and I don't know if this is my mistake (most likely) or a mistake in the UINavigationController.

I am using UINavigationController in my application. In some cases, I need complex navigation programs such as "pop 2 screen and push new one". I am currently doing this, getting the current navigationController.viewControllers , modifying the collection, and [navigationController setViewControllers:newStack animated:YES] .

It happens that this very often leads to an application crash. Typically, accidents are SIGBUS or SIGSEGV. The steps for playback are as follows:

  • Make one of these difficult navigations.
  • Go back one or two times depending on the type of navigation
  • Crash!

The interesting things are:

  • If the navigation was just β€œa few screens back,” then the next inverse error. If the navigation was β€œa few screens back and tap the new screen,” then the second back will work. Moreover, in such cases, navigators, such as back, forward, backward, backward, usually do not cause the application to crash
  • The strangest thing: if I do [navigationController popToRootViewControllerAnimated:NO] before updating the stack, the application does not crash or at least crashes much less often (I could not reproduce the crash).

An example of a trace of the crash stack captured 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 ()

Other:

  • 0x002945 mysigandler ()
  • 0x3293d82b _sigtramp ()
  • 0x31c5ead3 - [UIScrollView _updatePanWithStartDelta: event: gesture: ignoringDirectionalScroll:]
  • 0x31c5e435 - [UIScrollView handlePan:]
  • 0x31d14651 - [UITableView handlePan:]
  • 0x33590da7 - [Protocol selects: 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 "complex" navigation:

 @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 

Anyone now, what can I do wrong? I just don't have enough ideas.

Adding

I ran my application using NSZombieEnabled and MallocStackLogging to find out which object is failing. However, this did not give my reasonable results. To trace stack # 1, it is not executed in step 3 ( -[UIApplication sendAction:to:from:forEvent:] ), and the zombie object -[UIBarButtonItem performSelector:withObject:withObject:]: message sent to deallocated instance 0xa5f5f90 . This is the right navigation bar on the screen, to which the application takes 2 screens back (and remember that this work with two screens back works, only the next "normal" reverse navigation fails). But I am not doing anything with this button. Relevant code in ViewControler initWithSomething:(Something*)something :

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

The only feature of this button is that the onDone selector does this two-screen navigation backwards, but I don’t think it really matters. Therefore, I believe that something is wrong on the object of a higher level (perhaps View Controller or UINavigationController?). But what is wrong?

Addendum October 4, 2011:

Since people are still sometimes looking for this question, here is some code. My current approach to this problem is to use a custom subclass of UINavigationController instead with the 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 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: it’s better not to start animated navigation while the previous animation navigation is still ongoing (i.e. at least until calling the WillAppear view :).

+4
source share
3 answers

I think it would be worth running it with tools in zombie mode. This is almost certainly a memory problem, with something accessing an already released object.

+2
source

There was a similar problem. It turns out that, at least in some cases, the rightBarButtonItem action is called when the view controller displays. My ugly fix is ​​to remove the offending item when I push the next view controller onto the stack. Then I check viewWillAppear if rightBarButton is zero and recreates the button if necessary.

However, when you recreate the button, it will hover over 0, 0 for a split second before appearing on the right side of the navigation bar. A slightly more elegant solution would be to set the button action to the default value of "NULL". This solves the first problem, but also violates the backBarButtonItem function.

So, to summarize, I'm still looking for the right solution for this. I work throughout the day to contact Apple with this - it might even be a mistake to think about it ...

+1
source

For the second stack trace, I had the same problem. I released a view controller containing a scroll.

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

Something like that.

Hope this helps even after 9 months.

+1
source

Source: https://habr.com/ru/post/1302772/


All Articles