计时器可以指定绝对的日期和时间,以便到时执行任务也可以指定执行的任务的相对延迟时间,还可以重复运行任务。计时器要和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将其失效,若一次性的话,在其触发完任务之后也会失效。