NSOperationQueue和UITableView版本正在崩溃我的应用程序

这是迄今为止我一直困扰的最奇怪的问题。

我在UINavigationController上有一个UIViewController,我想使用NSInvocationOperation在viewDidAppear上调用一个方法,这样当视图变得可见时它可以在后台线程上运行。

问题是如果我在操作(在这种情况下是testMethod方法)完成运行之前弹出视图控制器,应用程序崩溃。

如果我在操作运行它之后弹出视图控制器,一切正常。

当应用程序崩溃时,它会以“EXC-BAD-ACCESS”停在[super dealloc]并给我以下错误。

bool _WebTryThreadLock(bool),xxxxxxxxx:试图从主线程或Web线程以外的线程获取Web锁。 这可能是从辅助线程调用UIKit的结果。 现在崩溃……

这是我的代码(超简化)..

- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSInvocationOperation *theOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testMethod) object:nil]; [operationQueue addOperation:theOperation]; [theOperation release]; } - (void)testMethod { NSLog(@"do some stuff that takes a few seconds to complete"); } - (void)dealloc { [_tableView release]; [super dealloc]; } 

testMethod有一些代码需要几秒钟才能完成。 我只有一些线索,我真的不知道如何以及从哪里开始调试这个。

  • 线索#1:最有趣的是,如果我删除[_tableView版本]; 从dealloc然后应用程序不会崩溃。 但当然这会导致泄漏,我无法将其删除。

  • 线索#2:我已经在一个带有UITableView的单独的“干净”UIViewController上测试了这个代码,令我惊讶的是它没有崩溃。

  • 线索#3:应用程序没有崩溃是在viewDidLoad中将UITableView的数据源设置为nil。

  • 线索#4:如果我在viewDidAppear中使用与IBAction等其他地方相同的代码,应用程序似乎不会崩溃。

  • 线索#5:我曾尝试用NSZombie查看堆栈数据,但它给了我大量的数据,这让我无处可去。

我的UITableViewDelegate和UITableViewDataSource中有一些非常复杂的代码,我真的不知道从哪里开始调试它。 我真的希望我不必逐行完成或重写整个事情因为这个。

关于我应该看的地方的任何指示?

问题很可能是你的视图控制器的最后一个引用持有它的操作队列,这意味着当操作清理时你在技术上调用(或有系统调用)后台线程中的一些UIKit方法(一个大禁忌) 。

为防止这种情况发生,您需要在操作结束时在主线程上向控制器发送保持活动消息,方法是将以下内容添加到testMethod的最后一行:

 [self performSelectorOnMainThread:@selector(description) withObject:nil waitUntilDone:NO]; 

在操作队列释放视图控制器之前,仍有可能处理此消息,但这种可能性很小。 如果它仍在发生,你可以这样做:

 [self performSelectorOnMainThread:@selector(keepAlive:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:NO]; - (void)keepAlive:(NSNumber *)fromBackground { if (fromBackground) [self performSelector:@selector(keepAlive:) withObject:nil afterDelay:1]; } 

通过在主线程上向视图控制器发送消息,它将使对象保持活动状态(NSObject保留视图控制器,直到主线程处理消息)。 如果在延迟后执行选择器,它还将使视图控制器保持活动状态。

你崩溃了,因为控制器仍然试图使用你的tableView引用,并且因为你使用了viewController,所以一切都将在dealloc中消失,而tableView仍在自己填充。 您可以尝试在dealloc方法中询问您的操作是否仍在运行,因此您可以取消它并且一切都应该没问题。

将操作添加到队列后,操作无法完成。 队列接管并处理该任务的调度。 但是,如果您稍后决定不想执行操作 – 因为用户在进度面板中按下取消按钮或退出应用程序 – 例如 – 您可以取消操作以防止它不必要地消耗CPU时间。 您可以通过调用操作对象本身的cancel方法或通过调用NSOperationQueue类的cancelAllOperations方法来完成此操作。

取消操作不会立即强制它停止正在进行的操作。 虽然期望所有操作都遵循isCancelled返回的值,但是您的代码必须显式检查此方法返回的值并根据需要中止。 NSOperation的默认实现包括检查取消。 例如,如果在调用start方法之前取消操作,则start方法退出而不启动任务。