无法将自己添加为子视图

时间:2013-11-19 22:19:13

标签: objective-c

Image of Crashlytics back trace from users in the field

我们使用Crashlytics,有30多位用户看到过此次崩溃。此崩溃日志来自该字段中的用户。我们从来没有能够重现它。这是在iOS7上运行的。不知道造成这种情况的原因,因为您可以看到调用堆栈中没有与我们的应用程序相关的内容。其他人看到这个或解决了这个问题?谢谢!请不要让我发布代码(见上面的评论)。

5 个答案:

答案 0 :(得分:34)

tl; dr - 请参阅底部的结论。

我也偶尔会收到用户关于此事的崩溃报告,然后今天对我自己感到侮辱。这是在我的表视图上选择一行试图推动一个新的导航控制器,然后我按下后退按钮时引起的。奇怪的是,我推出的导航控制器不包含任何数据。按下后,导航栏下的视图变黑,应用程序崩溃。

环顾四周,我所能找到的只有其他用户(hereherehere),这表明这是由于调用segue或快速连续两次推送视图控制器。但是,我只在pushViewController:内拨打tableView:didSelectRowAtIndexPath:一次。如果没有用户双击电池,我不明白为什么会发生这种情况。通过双击表格视图单元格进行测试无法重现观察到的崩溃。

但是,如果用户双击,因为主线程当时被阻止了怎么办? (我知道,我知道,永远不会阻止主线程!)

为了测试这个,我创建了一些(相当可怕,对不起,快速剪切并粘贴其他代码)类方法(在虚构的类MyDebugUtilities中)来重复阻塞和解除阻塞主线程,在之前启动它我打开了包含导致崩溃的表视图的视图控制器。这里是任何想要快速检查这个问题的人的代码(对[MyDebugUtilities repeatedlyBlockTheMainThread];的调用将使用信号量阻止主线程3秒,然后在3秒后将另一个调用排队到重复的BlockMainThread,广告无穷无尽。顺便说一下,你不想反复启动这种方法,或者它最终会一直阻塞):

+ (void)lowCostSemaphoreWait:(NSTimeInterval)seconds
{
    // Use a semaphore to set up a low cost (non-polling) delay on whatever thread we are currently running
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);

    dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_signal(semaphore);
    });

    NSLog(@"DELAYING...");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"END of delay\n\n");
}


+ (void)repeatedlyBlockTheMainThread
{    
    dispatch_async(dispatch_get_main_queue(), ^{
        // Block the main thread temporarily using a semaphore
        [MyDebugUtilities lowCostSemaphoreWait:3.0];

        // Queue up another blocking attempt to happen shortly
        dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
        dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [MyDebugUtilities repeatedlyBlockTheMainThread];
        });
    });
}

在我的tableView:didSelectRowAtIndexPath:中放置一个NSLog调用,然后尝试在主线程被阻止的时间段内双击一次确实重现了上面观察到的错误 - 按下后面的应用程序崩溃了很明显,tableView:didSelectRowAtIndexPath:被调用了两次。我想在这里发生的事情是,当主线程被阻塞时触摸排队,然后在释放后立即全部交付。当主线程未被阻止时双击会产生两次tableView:didSelectRowAtIndexPath:的来电,大概是因为它已经处理了第一次触摸并处理了推送。

因为这需要暂时阻止主线程,并且在精心设计的应用程序中这很少发生(如果有的话),这可以解释这个事实很难重现 - 我已经崩溃了从一小部分用户报告这一点,并且因为崩溃报告甚至没有指出我的哪个视图控制器触发了这个场景,我不知道在我亲身体验之前从哪里开始查看。

要解决这个问题,真正简单的解决方案是创建一个BOOL属性(selectionAlreadyChosen说),你在viewWillAppear:中设置为NO(所以当你回到这个屏幕时它会被重置) ,然后实现tableView:willSelectRowAtIndexPath:来执行以下操作:

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.selectionAlreadyChosen) {
        NSLog(@"BLOCKING duplicate calls to tableView:didSelectRowAtIndexPath:");
        return nil;
    }

    self.selectionAlreadyChosen = YES;
    return indexPath;
}

在您完成任务后,不要忘记删除任何NSLog以及对repeatBlockTheMainThead的调用。

这个确切的场景可能不是其他人正在经历的问题,但我认为苹果处理同时多个表格视图选择的方式存在问题 - 它不仅仅是双击 - 测试上述通过在主线程被阻止时疯狂地点击相同的单元格,生成一个BLOCKING duplicate calls to tableView:didSelectRowAtIndexPath:消息流。

实际上,在iOS 6上对此进行测试 - 对tableView:didSelectRowAtIndexPath:的多次调用没有被阻止,但这些调用的处理方式不同(当然这是反复调用pushViewController:的结果!) - 在iOS 6上主线程被阻止时多次点击会给你消息:

  

警告:尝试从视图控制器中解除    作为陈述或解雇   正在进行中!

就在被毫不客气地从UINavigationController中抛弃之前。因此,这可能不是因为这是一个iOS 7错误,但是处理已经改变了,并且一直存在的问题现在表现为崩溃。

<强>更新

通过我的项目寻找显示此问题的其他tableView,我发现。我能看到的唯一区别是这个工作的可编辑(因此支持切换进出编辑模式,移动行,然后滑动到删除),并且更接近我的视图层次结构的根。上面失败的那个是不可编辑的,并且在视图层次结构中 - 出现在一些模态段之后。我无法看到它们之间存在任何其他重大差异 - 它们都共享一个共同的超类 - 但是如果没有上述修复,则会反复尝试pushViewController:,而另一个则没有。

有趣的是,当它处于编辑模式时,没有出现问题的那个也会调用performSegueWithIdentifier:而不是pushViewController:,在这种情况下,它会反复调用{{ 1}}(以及tableView:didSelectRowAtIndexPath:) - 但有关调用performSegueWithIdentifier:的内容似乎取消了对pushViewController:的重复调用。

困惑?我知道我是。试图打破“工作”的过程。通过使其不可编辑来查看控制器什么都不做。用tableView:didSelectRowAtIndexPath:中的一个替换非编辑模式pushViewController:会导致多次调用segue,因此在tableView中处于​​编辑模式并不是什么事情。

将层次结构中的工作视图控制器替换为不存在的工作视图控制器(以便它通过rootViewController关系而不是模态段链接)固定以前的已破坏视图控制器(使用以前的解决方案取消了它。)

结论 - 有关于您的视图层次结构如何连线的问题,可能还有使用Storyboard或模态segue,或者我可能是一个破碎的视图层次结构导致这种情况 - 如果您的主线程恰好在用户双击时被阻止,则会导致表格视图单元格上的多次点击。如果您在performSegueWithIdentifier:内呼叫pushViewController:,则最终会被多次呼叫。在iOS 6上,这会将您从有问题的视图控制器中转出。在iOS 7上,这将导致崩溃。

答案 1 :(得分:2)

我在pushViewController/popViewControlleriOS7方法中viewDidLoadviewWillAppear时遇到了同样的崩溃。

当对同一个_block_invoke事件的现有调用正在运行时,似乎与为animateTransition调用#5上面的YES相关。为我工作,你的milage可能会有所不同。

我的堆栈看起来非常相似,但不完全一样。不确定这是否是由于我们的代码或什么的差异。我通过重构加载序列解决了我的问题,当我不能时,控制“animated:”的值,所以在一个并发事件上只有YES。

答案 2 :(得分:1)

我们的项目遇到了同样的问题。

我们的情景是: ViewController A有2个按钮。按钮1正在推动ViewController B和Button 2,正在将ViewController C推送到navigationController。

如果用户同时按下按钮1和2,之后尝试返回ViewController A(通过popViewController),则应用程序因您发布的日志而崩溃。 奇怪的是,这只发生在iOS 7上。

我们的临时解决方案是覆盖navigationController的pushViewController以具有自定义行为,以避免这种猴子场景。希望这可以帮助。

答案 3 :(得分:1)

如果您使用动画(动画:是)推动(或弹出)视图控制器,它不会立即完成,如果您在动画完成之前执行另一次推送或弹出,则会发生不良事件。

要重现此错误,请尝试同时推送或弹出两个视图控制器。例如:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    UIViewController *vc = [[UIViewController alloc] init];
    [self.navigationController pushViewController:vc animated:YES];
}

您将收到此错误:

  

2014-07-03 11:54:25.051 Demo [2840:60b]嵌套推送动画可以   导致导航栏损坏2014-07-03 11:54:25.406   演示[2840:60b]完成意外的导航过渡   州。导航栏子视图树可能已损坏。

这是一个完美的解决方案: https://github.com/macjam/SafeTransition

只需将代码文件添加到项目中,并将导航控制器作为APBaseNavigationController的子类,您就可以了。

答案 4 :(得分:0)

看起来嵌套/重叠推送导致了这种情况。 我为UINavigationController创建了一个简单的包装器,它可以防止嵌套的弹出和推送(https://github.com/Itheme/SmartPopNavigationController - 应该用于应用程序中的每个导航控制器才能正常工作) 在我在所有导航控制器上使用它后,无法重现此崩溃。不是100%肯定它不是巧合。