iOS 控制器被释放了定时器却还在执行其中方法的bug

先贴上此场景中的三处代码(此代码已解决该bug),场景及方法为中间注释部分:

//轮播图中的代理方法
- (void)carouselCurrentItemIndexDidChange:(iCarousel *)carousel {
    //翻页指示器
    self.pageControl.currentPage = carousel.currentItemIndex;
    //每次翻页改变时,就重置定时器
    [self timerFire];
}
#pragma mark 定时器启动

- (void)timerFire
{
    [self.timer invalidate];
    /**
    *   这里用_portrait来间接判断self是否被释放,原因如下:
    *   此处发现一个bug。
    *   当前界面为A,这处代码也都在A界面中, 从A push界面B,在B中发网络请求添加了验证当网络请求返回code=-2时,在B中popToRootViewController 然后切换根视图,
    *   控制器A还没来得及销毁,A中轮播图滚动时触发方法[carouselCurrentItemIndexDidChange:]中的方法[self timerFire],
    *   执行完 timerFire方法中的 [self.timer invalidate]后,立马进入dealloc方法,销毁对象,
    *   再接着执行 timerFire方法中的 [self.timer invalidate]后面的方法 __weak typeof(self) weakSelf = self;
    *   但是此时 self已经是被销毁的对象,(注意:虽然self被销毁,但它此时还不为空!因此此时不能在程序中使用self,否则会报对象已被释放的地址错误。但是通过观察控制台,self中的对象_portrait等已经被释放为空了。)
    *   在百度了两种方法之后(后面介绍),都不可行,因为此时程序中不能使用self,也就不能使用 self.portrait,但是我发现,可以使用 _portrait ,
    *   (self.portrait是先调用getter方法获值,而_portrait是直接访问对象的内存地址来获值)
    *   因此,使用_portrait 判断 _portrait是否为空,来间接判断 self是否被释放。(_portrait是viewDidLoad中创建的全局属性,只要self没被销毁,_portrait就不会为nil)。
    */
    if (_portrait) {
        __weak typeof(self) weakSelf = self;
        self.timer = [NSTimer bk_scheduledTimerWithTimeInterval:4 block:^(NSTimer *timer) {
            [weakSelf.carousel scrollToItemAtIndex:_carousel.currentItemIndex+1 animated:YES];
        } repeats:YES];
    }
}
- (void)dealloc {
    [self.timer invalidate];
    BLYLogDebug(@"销毁 PersonalInfoVC");
}
下面说一下我百度到的两种方法

1. 取消performSelector执行的方法

问:我有一个对象mytablecontroller,它有一个线程队列queue,每次需要请求table的数据的时候就添加一个operation去访问web,再performSelectorOnMainThread进行reloadtable。然而很有可能mytablecontroller在执行reloadtable方法前,用户就执行了导航切换了(navigationController popToViewController)。这时mytablecontroller被release了,而operation子线程还在继续执行,于是reloadtable就EXC_BAD_ACCESS了。我想给reloadtable添加一个判断,判断一下self.table是否被释放。请问我应该怎么做?if(self.table!=nil)我试过了,行不通,因为即使release了也不意味着就nil了。

有人回答: mytablecontroller这个类的dealloc方法里面,要cancel这个queue

所以我想起了使用 performSelector: 去调用方法是可以调用cancelPreviousPerformRequestsWithTarget:selector:object:取消的,详情见此文 iOS: NSObject中执行Selector的相关方法
试验后发现不行,我觉得可能是因为要用延时afterDelay才有效果performSelector:withObject:afterDelay:

2. 如何检测一个对象被释放

关于这个问题,我百度和stackoverflow上都没有找到可靠的答案,现在来求助各位大神。
网上普遍提到的一种方法是使用如下方式:

UIView *myView = [[UIView alloc] init];
    Class oldClass = object_getClass(myView);
    [myView release];
    Class newClass = object_getClass(myView);
    if(oldClass == newClass){
        //not released
    }
    else {
        //released
    }

在iOS8.3上测试可用,在iOS6和iOS7测试都不可用

但是 我在注释的场景中说了,这个对象当前控制器self,而不是self中的属性,dealloc后不能再程序中使用self,所以此思路也无法使用。

总结:

这个bug是在界面A push界面B,在B中发请求添加了验证token=-2要退出切换根视图时才遇到的,可能是我用定时器控制轮播图滚动的逻辑不太好。

你可能感兴趣的:(iOS 控制器被释放了定时器却还在执行其中方法的bug)