我遇到了一个奇怪的问题,我不知道这是我的错误(最有可能)还是 UINavigationController 中的错误。
我在我的应用程序中使用 UINavigationController。在某些情况下,我需要复杂的导航,例如“弹出 2 个屏幕并推送新的屏幕”。目前我通过获取当前navigationController.viewControllers
来做到这一点,修改集合,并调用[navigationController setViewControllers:newStack animated:YES]
.
这使得我的应用程序经常崩溃。通常崩溃是 SIGBUS 或 SIGSEGV。重现步骤如下:
- 进行复杂的导航之一
- 根据导航类型返回一到两次
- 崩溃!
有趣的是:
- 如果导航只是“返回几个屏幕”,那么下一个返回就会崩溃。如果导航是“返回几个屏幕并推送新屏幕”,那么第二个返回将会崩溃。此外,在这种情况下,后退、前进、后退、后退等导航通常不会导致应用程序崩溃
- 最奇怪的是:如果我这样做
[navigationController popToRootViewControllerAnimated:NO]
在更新堆栈之前,应用程序不会崩溃,或者至少崩溃的情况要少得多(我无法重现崩溃)。
我的信号处理程序捕获的崩溃堆栈跟踪示例:
- 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()
另一个:
- 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()
“复杂”导航实现示例:
@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
现在有人知道我可能做错了什么吗?我已经没有主意了。
添加:
我确实使用 NSZombieEnabled 和 MallocStackLogging 运行我的应用程序来找出失败的对象。然而它并没有给出我合理的结果。对于堆栈跟踪 #1,它在步骤 3 失败 ( -[UIApplication sendAction:to:from:forEvent:]
) 并且僵尸对象是
-[UIBarButtonItem performSelector:withObject:withObject:]: message sent to deallocated instance 0xa5f5f90
。这是应用程序从 2 个屏幕返回的屏幕的右侧导航栏按钮(请记住,此 2 个屏幕返回导航有效,只有下一个“通常”返回导航失败)。但我没有用那个按钮做任何事情。对应代码在ViewControler的initWithSomething:(Something*)something
是:
UIBarButtonItem* doneItem = [[UIBarButtonItem alloc] initWithTitle:@"Complete"
style:UIBarButtonItemStyleDone
target:self action:@selector(onDone)];
self.navigationItem.rightBarButtonItem = doneItem;
[doneItem release];
这个按钮唯一特别的地方是 onDone
选择器执行 2 个屏幕后退导航,但我认为这并不重要。所以我相信更高级别的对象(可能是 View Controller 或 UINavigationController?)有问题。但出了什么问题呢?
2011 年 10 月 4 日添加:
由于人们有时仍然在搜索这个问题,这里有一些代码。我当前解决此问题的方法是使用 UINavigationController 的自定义子类,而不是通过以下 hack 来代替它(不保证这有效或仍然必要):
@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
另一件重要的事情:当之前的动画导航仍在进行时,最好不要启动动画导航(即至少在调用 viewWillAppear: 之前)。
最佳答案
我认为在僵尸模式下通过 Instruments 运行它是值得的。这几乎肯定是一个内存问题,因为某些东西访问了已经释放的对象。
关于iphone - UINavigationController setViewControllers 导致应用程序崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2357698/