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

时间:2012-01-30 18:26:27

标签: objective-c ios

我在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 <UIWebViewDelegate>
    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)吗?

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

4 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:0)

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


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

<击>

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

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

你不应该设计你的dealloc来启动很多逻辑 - 如果你的对象在它完成处理你期望的逻辑之前接收到dealloc,那么你允许它被释放得太快。

答案 2 :(得分:0)

当保留计数降至0时将调用

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

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

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

答案 3 :(得分:0)

您正确地在-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()

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