无法将自己添加为子视图 [英] Can't add self as subview

查看:79
本文介绍了无法将自己添加为子视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们使用Crashlytics,已有30多个用户看到此崩溃.此崩溃日志来自该字段中的用户.我们从来没有能够复制它.它正在iOS7上运行.不知道是什么原因造成的,因为您可以看到调用堆栈中没有与我们的应用程序相关的任何内容.其他人看到了这个或解决了这个问题吗?谢谢!请不要要求我发布代码(请参阅上面的评论).

解决方案

tl; dr-参见底部的结论.

我也经常从用户那里收到有关此问题的崩溃报告,然后今天对我自己来说却很卑鄙.这是由于在我的表格视图中选择一行试图推送新的导航控制器,然后按下返回按钮而引起的.奇怪的是,我推送的导航控制器不包含任何数据.按下后,导航栏下的视图变黑,应用程序崩溃了.

环顾四周,我只能找到其他用户(此处此处

在我的tableView:didSelectRowAtIndexPath:中放置一个NSLog调用,然后尝试在主线程被阻塞的时间段内双击,确实确实重现了以上观察到的错误-按下后使该应用程序崩溃,并导致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;
}

完成操作后,别忘了删除任何NSLogs和对repeatingBlockTheMainThead的调用.

这种确切的情况可能不是其他所有人都遇到的问题,但我认为Apple处理同时选择多个表视图的方式存在问题-不仅是两次敲击-疯狂地测试上述解决方案主线程被阻塞时,同一单元格会生成BLOCKING duplicate calls to tableView:didSelectRowAtIndexPath:消息流.

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

警告:尝试从视图控制器中关闭 演讲或解雇时 正在进行中!

正好被毫不客气地从该UINavigationController中转出.因此,这可能不是iOS 7的一个错误,而是其处理方式已经在幕后发生了变化,而且一直存在的问题现在表现为崩溃.

更新:

在我的项目中查找存在此问题的其他tableView,我发现了一个没有的问题.我可以看到的唯一区别是,该工作菜单是可编辑的(因此支持切换进出编辑模式,移动行以及滑动以删除),并且更接近于我的视图层次结构的根.上面失败的那个是不可编辑的,并且位于视图层次结构的下方-出现在许多模态选择之后.我看不到它们之间的任何其他主要区别-它们都共享一个公共的超类-但是一个尝试重复进行pushViewController:而不进行上述修复,而另一个尝试则没有.

有趣的是,没有出现问题的人在编辑模式下也调用了performSegueWithIdentifier:而不是pushViewController:,在这种情况下,它会反复调用tableView:didSelectRowAtIndexPath:(因此是)-但是有关调用pushViewController:的某些事情似乎可以取消对tableView:didSelectRowAtIndexPath:的重复调用.

困惑吗?我知道我是.试图通过使其无法编辑来破坏工作"视图控制器,则无济于事.用performSegueWithIdentifier:之一替换非编辑模式pushViewController:会导致多次调用segue,因此与tableView中处于​​编辑模式无关.

用不存在的视图控制器替换层次结构中的工作视图控制器(以便通过rootViewController关系而不是模态链接进行链接)修复了先前损坏的视图控制器(使用先前的修复方法)取出来.)

结论-有关如何连接视图层次结构的某些问题,也许是使用情节提要或模态选择的原因,或者我的视图层次结构破裂导致此问题-导致多个如果您的主线程碰巧在用户双击的那一刻被阻塞,则一次发送所有一个表视图单元格上的"tap".如果要在tableView:didSelectRowAtIndexPath:中调用pushViewController:,它将最终被多次调用.在iOS 6上,这会将您转出令人讨厌的视图控制器.在iOS 7上,这将导致崩溃.

We use Crashlytics, 30+ users have seen this crash. this crash log is from users in the field. we have never been able to reproduce it. This is running on iOS7. No clue what is causing this, as you can see there is nothing in the call stack relating to our app. Anyone else see this or solved the issue? thanks! Please don't ask me to post code (see comments above).

解决方案

tl;dr - see the Conclusion at the bottom.

I too have been getting occasional crash reports through from users regarding this, and then today suffered the indignity of it for myself. This was caused when selecting a row on my table view attempted to push a new nav controller, and then I pressed the back button. Strangely the nav controller I pushed contained no data. After pressing back, the view under the nav bar went black and the app crashed.

Looking around, all I could find was other users (here, here and here) suggesting this was down to calling a segue or pushing a view controller twice in rapid succession. However, I call pushViewController: from within my tableView:didSelectRowAtIndexPath: only once. Short of the user double tapping on a cell, I don't see why this could happen to me. Testing by double tapping a table view cell failed to reproduce the observed crash.

However, what if the user had double tapped because the main thread happened to be blocked at the time? (I know, I know, never ever block the main thread!)

To test this, I created some (rather horrific, sorry, quickly cut and paste from other code) class methods (in the fictional class MyDebugUtilities) to repeatedly block and unblock the main thread, launching it just before I opened the view controller containing the table view that caused the crash. Here's the code for anyone who wants to quickly check this for themselves (a call to [MyDebugUtilities repeatedlyBlockTheMainThread]; will block the main thread for 3 seconds using a semaphore, then queue up another call to repeatedlyBlockTheMainThread 3 seconds later, ad infinitum. BTW, you don't want to launch this method repeatedly or it'll end up just blocking all the time):

+ (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];
        });
    });
}

Putting a NSLog call in my tableView:didSelectRowAtIndexPath:, and then attempting to double tap in one of the time periods when the main thread was blocked did indeed reproduce the bug as observed above - pressing back crashed the app, and from the NSLog it was obvious tableView:didSelectRowAtIndexPath: was getting called twice. I think here what is happening is that the touches are getting queued up while the main thread is blocked, and then delivered all-together as soon as it is freed up. Double-tapping when the main thread is not blocked does not generate two calls to tableView:didSelectRowAtIndexPath:, presumably because it's already processed the first touch and handled the push.

Because this requires the main thread to be blocked temporarily, and in a well crafted app this should happen rarely (if at all), this would explain the fact that this is really hard to reproduce - I've had crash reports for this from a tiny percentage of users, and because the crash reports didn't even indicate which of my view controllers had triggered the scenario, I had no idea of where to start looking until I experienced it for myself.

To solve this, the really simple solution is to create a BOOL property (selectionAlreadyChosen say), which you set to NO in viewWillAppear: (so when you go back to this screen it gets reset), and then implement tableView:willSelectRowAtIndexPath: to do something like:

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

    self.selectionAlreadyChosen = YES;
    return indexPath;
}

Not forgetting to remove any NSLogs and the call to repeatedlyBlockTheMainThead when you're done.

This exact scenario might not be the problem everyone else is experiencing, but I think there's a problem in the way Apple handles simultaneous multiple table view selections - it isn't just double-taps - testing the above solution by wildly tapping on the same cell when the main thread was blocked, generated a stream of BLOCKING duplicate calls to tableView:didSelectRowAtIndexPath: messages.

Actually, testing this on iOS 6 - the multiple calls to tableView:didSelectRowAtIndexPath: were not prevented, but these were handled differently (this being the result of calling pushViewController: repeatedly of course!) - on iOS 6 multiple taps while the main thread was blocked would give you the message:

Warning: Attempt to dismiss from view controller while a presentation or dismiss is in progress!

just before being unceremoniously dumped out of that UINavigationController. So it's maybe not so much that this is an iOS 7 bug, but that the handling has changed under the covers, and a problem that was always there is now manifesting as a crash.

Update:

Going through my project looking for other tableView's that exhibit this problem, I've found one that doesn't. The only differences I can see are that this working one is editable (so supports switching in and out of edit mode, moving rows, and swipe to delete), and is closer to the root of my view hierarchy. The one that had been failing above was not editable, and is way down the view hierarchy - appearing after a number of modal segues. I can't see any other major differences between them - they both share a common superclass - but one attempts to pushViewController: repeatedly without the above fix, and the other doesn't.

Interestingly, the one that doesn't exhibit the problem also calls performSegueWithIdentifier: instead of pushViewController: when it is in editing mode, and when this is the case, it DOES repeatedly call tableView:didSelectRowAtIndexPath: (and hence performSegueWithIdentifier:) - but something about calling pushViewController: seems to cancel the repeated calls to tableView:didSelectRowAtIndexPath:.

Confused? I know I am. Trying to break the 'working' view controller by making it non-editable does nothing. Replacing the non-edit mode pushViewController: with one of the performSegueWithIdentifier: caused the segue to be called multiple times, so it isn't anything to do with being in edit mode in the tableView.

Replacing the working view controller in the hierarchy with the one that doesn't (so that it was linked by rootViewController relationships rather than modal segues) FIXED THE PREVIOUSLY BROKEN VIEW CONTROLLER (with the previous fix taken out of it).

Conclusion - there's something about how your view hierarchy is wired up, and maybe down to using Storyboards, or modal segues, or maybe I've a broken view hierarchy that causes this - that causes multiple taps on a table view cell to be sent all at once if your main thread happens to be blocked at the instant the user double taps. If you are calling pushViewController: within your tableView:didSelectRowAtIndexPath: it will end up being called multiple times. On iOS 6 this will dump you out of the offending view controller. On iOS 7 this will cause a crash.

这篇关于无法将自己添加为子视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆