MRC环境下NSTimer与assign delegate产生的崩溃

660 阅读2分钟

demo地址 github.com/Jack2627/mr…

首先介绍一下产生崩溃的业务逻辑,为了方便介绍崩溃产生的原因抽象为两个类:ShowAdViewController,SimulateAd,可以看一下类图:

image.png

这个业务可以理解成加载广告,同时客户端自己维护一个timer处理超时,即timer计时结束后不再接收_ad的回调。当进入ShowAdViewController页面并点击开始加载广告时引用关系如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    [self actionRequestAd];
}

#pragma mark - Private
- (void)actionRequestAd{
    if (!_adObj) {
        _adObj = [[SimulateAd alloc] init];
        _adObj.delegate = self;
    }
    [_adObj requestAd];
    [self stopTimer];
    
    //隐性的[self retain]
    _timer = [[NSTimer scheduledTimerWithTimeInterval:3 target:self selector: @selector(actionTimeout:) userInfo:nil repeats:NO] retain];
    self.title = @"Requesting";
}

- (**void**)actionTimeout:(NSTimer *)timer{

    [**self** stopTimer];

    if (_adObj) {
        _adObj.delegate = nil;
        [_adObj release]; _adObj = nil;
    }

    self.title = @"Timeout";
}

image.png _timer设置的时间是5s,_ad中模拟返回广告的时间是2s,如果在2s内退出该页面,Simulate回调结果时将产生崩溃。下面通过看代码和分析引用分析图找到崩溃的原因:


#pragma mark - SimulateAdDelegate
- (void)sim_finishedRequest{
    /*
     SimulateAd的回调方法
     正常情况下即页面没有退出,nav保持对ShowAdViewController(self)的持有,在广告结果回调中结束time是正确的流程
     如果页面已经pop,nav已经不会对self有强引用,调用stopTime会调用[_timer invalidate]此时runtime将释放对self的强引用,所以会进入dealloc流程
     */
    [self stopTimer];

    //如果已经完成dealloc流程再调用self.title即为访问悬垂指针,将会造成崩溃
    self.title = @"Success";

}


#pragma mark - Private

- (void)stopTimer{

    if (_timer) {

        [_timer invalidate];    //隐性的[self release],如果此时self没有其他的引用,将进入dealloc,所以在stopTimer后再调用self即是对已经释放过的对象发送消息,造成崩溃

        [_timer release]; _timer = nil;

    }

}

根据调用栈可以得出以下结论: nav将pop ShowAdViewController所以不再对其引用;

2s后收到SimulateAd的回调,进入sim_finishedRequest代理方法中,[_timer invalidate]调用后runloop将对ShowAdViewController(vc_2), _timer发送release消息,[_timer release]将解除ShowAdViewController对_timer的强引用;

在调用完[self stopTimer]后已经没有任何强引用指向vc_2了,所以进入ShowAdViewController dealloc流程,而再下一步对self.title的访问就会造成崩溃。

image.png