多线程 ViewController 中的 UIWebView

Posted

技术标签:

【中文标题】多线程 ViewController 中的 UIWebView【英文标题】:UIWebView in multithread ViewController 【发布时间】:2010-10-31 00:18:46 【问题描述】:

我在视图控制器中有一个 UIWebView,它有以下两种方法。问题是如果我在第二个线程完成之前弹出(点击导航栏)这个控制器,应用程序将在 [super dealloc] 之后崩溃,因为“试图从主线程以外的线程获取网络锁或web 线程。这可能是从辅助线程调用 UIKit 的结果。”。任何帮助将不胜感激。

-(void)viewDidAppear:(BOOL)animated 
    [super viewWillAppear:animated];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(load) object:nil];
    [operationQueue addOperation:operation];
    [operation release];


-(void)load 
    [NSThread sleepForTimeInterval:5];
    [self performSelectorOnMainThread:@selector(done) withObject:nil waitUntilDone:NO];

【问题讨论】:

My Solution (uses an NSTimer for the last release) 【参考方案1】:

这是一些在主线程上运行 UIKit 元素的代码。如果您正在处理不同的线程并需要运行一段 UIKit 代码,只需将它放在这个 Grand Central Dispatch sn-p 的括号之间。

dispatch_async(dispatch_get_main_queue(), ^

    // do work here

);

【讨论】:

【参考方案2】:
- (void)dealloc

    if(![NSThread isMainThread]) 
        [self performSelectorOnMainThread:@selector(dealloc) 
                               withObject:nil 
                            waitUntilDone:[NSThread isMainThread]];
        return;
     
    [super dealloc];

【讨论】:

贴代码的时候最好加上简短的解释【参考方案3】:

我有相同的解决方案,其中后台线程是最后一个版本,导致视图控制器的解除分配发生在后台线程中,最终导致相同的崩溃。

上述[[self retain] autorelease] 仍然会导致最终释放发生在后台线程的自动释放池中。 (除非自动释放池中的版本有什么特别之处,否则我很惊讶这会有所作为)。

我发现这是我的理想解决方案,将此代码放入我的视图控制器类中:

- (oneway void)release

    if (![NSThread isMainThread]) 
        [self performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
     else 
        [super release];
    

这确保了我的视图控制器类的release 方法总是在主线程上执行。

我有点惊讶,某些只能从主线程正确释放的对象还没有内置这样的东西。哦,好吧...

【讨论】:

这结束了我的头疼问题。谢谢! 很高兴我找到了这个 - 2011 年 4 月 非常感谢。你节省了我的时间 我可以把self performSelectorOnMainThread:改成super performSelectorOnMainThread:吗? 当然。改成 super 是有道理的 - 不错的收获!【参考方案4】:

我试过了:

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

这似乎效果更好。

【讨论】:

【参考方案5】:

我尝试了上面发布的两种解决方案,[operationQueue cancelAllOperations][[self retain] autorelease]。但是,通过快速单击,仍然存在保留计数降至 0 并且类在辅助线程上被释放的情况。为了避免崩溃,我现在将以下内容放在我的dealloc 中:

    if ([NSThread isMainThread]) 
        [super dealloc];
    

这是一个明显的泄漏,但似乎是两害相权取其轻。

欢迎遇到此问题的任何人提供任何其他见解。

【讨论】:

【参考方案6】:

我目前在我的应用程序中遇到了类似的问题。显示 UIWebView 的视图控制器被推送到导航控制器并启动后台线程以检索数据。如果您在线程完成之前点击后退按钮,应用程序将崩溃并显示相同的错误消息。

问题似乎是NSThread 保留了目标(自身)和对象(参数),并在方法运行后释放它——不幸的是,它从线程内释放了两者。因此,当创建控制器时,保留计数为 1,当线程启动时,控制器的保留计数为 2。当您在线程完成之前弹出控制器时,导航控制器释放控制器,从而导致保留计数为 1。到目前为止,这很好——但是如果线程最终完成,NSThread 将释放控制器,这导致保留计数为 0 并从线程内立即释放。这使得 UIWebView(在控制器的 dealloc 方法中释放)引发该线程警告异常并崩溃。

我通过使用[[self retain] autorelease] 作为线程中的最后一条语句(就在线程释放其池之前)成功地解决了这个问题。这确保了控制器对象不会立即释放,而是标记为自动释放并稍后在主线程的运行循环中释放。然而,这是一个有点肮脏的黑客,我宁愿找到一个更好的解决方案。

【讨论】:

您的“变通”解决方案对我有用,谢谢。顺便说一句,Tao,您对使用哪种方法的最后决定是什么? 实际上你正在泄漏内存...!!【参考方案7】:

根据您的代码,我不确定具体发生了什么,但看起来 viewDidAppear 被调用并创建了第二个线程,然后您离开控制器并释放它,然后第二个线程完成并在释放的对象“self”上调用 performSelectorOnMainThread。我想你可能只需要检查一下释放没有发生吗?

您收到的错误消息表明您正在从第二个线程运行一些 UIKit 代码。 Apple 最近为 UIKit 的线程调用添加了一些检查,我认为您可能只需要重构加载函数以更新主线程上的 UI,而不是从第二个线程调用 UIWebView 函数。

希望有帮助!

【讨论】:

【参考方案8】:

一般来说,当使用它们的视图消失时,您应该取消所有后台操作。如:

- (void)viewWillDisappear:(BOOL)animated 
  [operationQueue cancelAllOperations];
  [super viewWillDisappear:animated;

【讨论】:

感谢您的回复。但是我已经添加了这一点,并且似乎总是在辅助线程中调用 dealloc。仍然无法弄清楚原因。 取消后台操作是“正确”的做法。它不能解决问题的原因是cancelAllOperations 不会自动中止已经运行的操作。您需要做的是在您的load 方法中,在线程休眠后,检查操作的isCancelled 属性;如果是这样,请返回而不调用done。但是,如果您只是在后台线程中暂停,那么更简单的方法是使用performSelector:withObject:afterDelay:

以上是关于多线程 ViewController 中的 UIWebView的主要内容,如果未能解决你的问题,请参考以下文章

多线程 线程的安全隐患

多线程与通知

多线程与通知

多线程与通知

多线程经常使用的函数

多线程 CGD快速迭代