demo地址 github.com/Jack2627/mr…
首先介绍一下产生崩溃的业务逻辑,为了方便介绍崩溃产生的原因抽象为两个类:ShowAdViewController,SimulateAd,可以看一下类图:
这个业务可以理解成加载广告,同时客户端自己维护一个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";
}
_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的访问就会造成崩溃。