NSTimer产生的问题及解决方案

来源:互联网 发布:网络交换机怎么安装 编辑:程序博客网 时间:2024/05/18 07:22

  计时器可以指定绝对的日期和时间,以便到时执行任务也可以指定执行的任务的相对延迟时间,还可以重复运行任务。计时器要和runloop相关联,运行循环到时候会触发任务。虾米昂这个方法可以创建并预先安排到当前运行循环中:
  

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

  target与selector参数表示计时器将在哪个对象上调用哪个方法。可以指定时间执行任务,也可以令其反复执行任务,直到自己手动将其关闭。计时器会保留其目标对象,等其失效时再释放此对象。若是重复模式需自己调用invalidate才能令其停止。

  由于计时器会保留其目标对象,所以反复执行任务会导致“保留环”(即循环引用)。问题代码如下:

#import "testViewController.h"@interface testViewController (){    NSTimer *_time;}- (void)startPolling;- (void)stopPolling;@end@implementation testViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];    btn.backgroundColor = [UIColor redColor];    [btn addTarget:self action:@selector(clickBtn) forControlEvents:UIControlEventTouchUpInside];    [self.view addSubview:btn];    [self startPolling];}-(void)dealloc{    NSLog(@"testVC dealloc");    [_time invalidate];}-(void)startPolling{    _time = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doPoll) userInfo:nil repeats:YES];}-(void)stopPolling{    [_time invalidate];    _time = nil;}- (void)doPoll{    NSLog(@"do something");}- (void)clickBtn{    [self dismissViewControllerAnimated:YES completion:nil];}@end

计时器的目标对象是self,所以保留此实例。但计时器是实例变量存放的,所以实例也保留了计时器。于是就产生了“保留环”。若想在系统回收奔雷实例的时候令计时器无效,从而打破保留环,那会陷入死结,因为_time对象有效时,该实例对象保留计数不会降为0,因为也不会调用dealloc方法,从而也无法调用invaildate方法,所以计时器一直处于有效果状态。从而导致内存泄露。若想释放必须主动调用stopPolling方法,坦诺作为公开API给别人使用,无法保证他人一定会调用此方法。

  解决方式:用block可解决,可为计时器添加以下功能:
  

#import <Foundation/Foundation.h>@interface NSTimer (JBlocksSupport)+ (NSTimer *)j_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;@end@implementation NSTimer (JBlocksSupport)+ (NSTimer *)j_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats{    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(j_blockInvoke:) userInfo:[block copy] repeats:repeats];}+ (void)j_blockInvoke:(NSTimer *)timer{    void (^block) () = timer.userInfo;    if (block) {        block();    }}@end

  解释:将计时器执行的任务封装成block,调用该方法时将block作为useInfo参数传进去。只要计时器有效,就会一直保留block,传入时需拷贝到“堆”上,否则可能会失效。target是类对象,此处也有保留环,但类对象无需回收,因此无需担心。使用方法如下:
  

-(void)startPolling{    __weak testViewController *weakSelf = self;    _time = [NSTimer j_scheduledTimerWithTimeInterval:1 block:^{        [weakSelf doPoll];    } repeats:YES];}

  先定义弱引用,令其指向self,块捕获该弱引用,即self不会被计时器所保留。这样,在外界指向该类的实例的最后一个引用将其释放,则该实例便可被系统回收,会调用dealloc方法,还会调用invalidate方法,使计时器失效。

  记住:若将计时器设置为重复,则需调用invalidate将其失效,若一次性的话,在其触发完任务之后也会失效。
  

0 0