Objective C – iOS – 在执行webViewDidFinishLoad的过程中调用Dealloc

我在ios中遇到内存管理问题。 问题是当我将具有webView的viewController推送到导航堆栈时,当我在加载webview之前单击后退时,我得到了exec_bad_access。

在’A类’我正在创建一个NewViewController,然后我将它推到导航堆栈,然后释放它。 所以我在这里放弃了我的所有权。

A类:

-(void)onButtonClick{ NewViewController* viewController = [[NewViewController alloc] init]; [self.navigationController pushViewController: viewController........]; [viewController release]; } 

B类有一个webView和一个计时器,并实现了UIWebViewDelegate。 所以,在这里当webView应该是StartLoad时,我正在启动计时器。 然后当它完成加载时我会使它失效。

B级:

 @interface NewViewController : UIViewController  NSTimer* timer ...... @property(nonatomic, retain) IBOutlet UIWebView* webView; @end @implementation -(void)viewDidLoad{ [super viewDidLoad]; [webView loadRequest:someRequest]; } ..... ..... -(void)dealloc{ [self makeTimerNil]; [self.webView stoploading]; self.webView.delegate = nil; [self.webView release]; self.webView = nil; ..... [super dealloc]; } -(void)resetTimer{ [self makeTimerNil]; //timer will retain target - self timer = [NSTimer scheduledTimerWithTimeInterval:kNetworkTimeOut target:self selector:@selector(networkTimedOut) userInfo:nil repeats:NO]; } -(void)makeTimerNil{ if([timer isValid]){ [timer invalidate]; timer = nil; } } -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ [self resetTimer]; ...... return YES; } -(void)webViewDidFinishLoad:(UIWebView *)webView{ //NO Exception. Can access self [self anotherMethod]; //timer releases retained target -self [self makeTimerNil]; //Exception self has been deallocated [self anotherMethod]; } @end 

但问题是当我点击导航栏上的后退按钮加载webView时,newViewController正在取消分配,这很好。 但这是在执行webViewDidFinishLoad的过程中发生的。 为什么在执行webViewDidFinishLoad的过程中调用dealloc? 它们不是在同一个线程上运行(Main – UI Thread)吗?

有关如何解决问题的任何想法?

你的问题绝对是计时器。 根据NSTimer文档,活动计时器在其目标对象上保留一个保留。 因此,当计时器处于活动状态时,您的控制器无法取消激活。 这本身就是您架构中的一个错误,因为从您的-dealloc方法来看,显然您期望在计时器处于活动状态时取消分配视图控制器。 但在webview的情况下,它引起了另一个问题。 具体来说,在-webViewDidFinishLoad:方法的中间,您取消了计时器。 这会导致它释放其目标,并且由于它是视图控制器的唯一所有者,因此视图控制器会立即释放deallocs。

当保留计数降至0时将调用dealloc ,并且将立即发生,而不是稍后发生。 它发生在计数下降到0的线程中,在你的情况下调用webViewDidFinishLoad:的线程。

您可以在此处执行的操作是在webViewDidFinishLoad:方法的顶部添加[self retain] ,在其底部添加[self release] 。 但是,我不能100%确定是否保证在主线程上运行该方法,并且如果视图控制器在主线程以外的线程上被解除分配,则可能存在许多问题。

要真正解决这个问题,我会覆盖viewDidDisappear:并在那里完成所有Web视图的拆除(即将委托设置为nil并停止计时器等)。 这样就会发生在主线程上,点击后退按钮。

您正确地在-dealloc中删除了Web视图的委托。

认为 (但这可能是错误的),作为UI委托方法,Web视图委托方法将始终在主线程上,因此这不应该是一个问题。 没有其他地方可以在这​​里发挥作用。

但是,作为一个可能的问题,让我感到困惑的是与您的网络视图代码本身无关。

考虑之间的相互作用

 @property(nonatomic, retain) IBOutlet UIWebView* webView; 

 [self.webView release]; self.webView = nil; 

既然你已经将webView声明为保留,那么self.webView = nil会不会向webView发送一个版本(并保留为nil,这当然不会做任何事情)? 那么这不是webView的过度发布吗?

(作为一般规则,您不应在init / dealloc中使用访问器,部分原因是副作用。)

编辑:

为了进一步研究这个问题,我使用基于视图的应用程序模板编写了一个快速测试。 以下相关部分:

 // In MyClass.m + (id)alloc { NSLog(@"alloc"); return [super alloc]; } - (void)dealloc { NSLog(@"dealloc"); [super dealloc]; } - (oneway void)release { NSLog(@"release"); [super release]; } - (id)retain { NSLog(@"retain"); return [super retain]; } // In UIViewController subclass // .h: #import "MyClass.h" @property (retain, nonatomic) MyClass *myObj; // .m: @synthesize myObj = _myObj; - (void)viewDidLoad { [super viewDidLoad]; [self setMyObj:[[[MyClass alloc] init] autorelease]]; [self setMyObj:nil]; } 

这产生了以下输出,表明正如我所料,设置nil会释放旧对象:

 2012-01-30 11:04:37.277 ReleaseTest[56305:f803] alloc 2012-01-30 11:04:37.278 ReleaseTest[56305:f803] retain 2012-01-30 11:04:37.279 ReleaseTest[56305:f803] release 2012-01-30 11:04:37.282 ReleaseTest[56305:f803] release 2012-01-30 11:04:37.283 ReleaseTest[56305:f803] dealloc 

不出所料,添加一个无关的版本会使EXC_BAD_ACCESS崩溃:

 - (void)viewDidLoad { [super viewDidLoad]; [self setMyObj:[[[MyClass alloc] init] autorelease]]; [[self myObj] release]; [self setMyObj:nil]; } 2012-01-30 11:06:22.815 ReleaseTest[56330:f803] alloc 2012-01-30 11:06:22.817 ReleaseTest[56330:f803] retain 2012-01-30 11:06:22.818 ReleaseTest[56330:f803] release 2012-01-30 11:06:22.818 ReleaseTest[56330:f803] release 2012-01-30 11:06:22.819 ReleaseTest[56330:f803] dealloc objc_release --> EXC_BAD_ACCESS in UIApplicationMain() 

当然,如果使用点语法,代码的行为相同(尽管点语法具有隐藏它使用访问器这一事实的不幸影响,这是我不使用它的一个原因)。 我还使用IBOutlet进行测试,而不是在代码中实例化。 更多的保留/释放,同样不出所料,但如果我包含一个额外的版本,仍然会崩溃:

 2012-01-30 11:09:59.631 ReleaseTest[56389:f803] alloc 2012-01-30 11:09:59.633 ReleaseTest[56389:f803] retain 2012-01-30 11:09:59.634 ReleaseTest[56389:f803] retain 2012-01-30 11:09:59.635 ReleaseTest[56389:f803] retain 2012-01-30 11:09:59.636 ReleaseTest[56389:f803] retain 2012-01-30 11:09:59.636 ReleaseTest[56389:f803] retain 2012-01-30 11:09:59.637 ReleaseTest[56389:f803] release 2012-01-30 11:09:59.638 ReleaseTest[56389:f803] release 2012-01-30 11:09:59.638 ReleaseTest[56389:f803] release 2012-01-30 11:09:59.639 ReleaseTest[56389:f803] release 2012-01-30 11:09:59.640 ReleaseTest[56389:f803] release 2012-01-30 11:09:59.641 ReleaseTest[56389:f803] release 2012-01-30 11:09:59.641 ReleaseTest[56389:f803] dealloc objc_msgSend --> EXC_BAD_ACCESS in UIApplicationMain() 

所以,总而言之,我相信你的代码中确实存在内存管理错误,这对你的一些(如果不是全部)崩溃负责(我注意到,这与我实验所复制的那种相同)。

编辑:这篇文章是错误的,与OP无关。 消除误导性文本。 在这里rest,因为我在Kevin的评论中学到了一些东西,这可能对其他人有帮助!


如果您不需要在dealloc上进行webViewDidFinishLoad处理,请设置self.webView.delegate = nil; 在调用stopLoading之前。

此外,如果需要任何处理,您可以在dealloc中手动调用webViewDidFinishLoad处理,并且不依赖于webView的状态。

我真的不建议在你的dealloc中加入一个stopLoading 。 只需将委托设置为nil即可。

您不应该设计dealloc来启动大量逻辑 – 如果您的对象在完成处理您期望的逻辑之前接收到dealloc,那么您允许它过快地释放它。