NSTimer深入理解

NSTimer,即计时器,用于定时执行一些任务,一次或者多次。

系统Foundation框架提供的最常用方法如下,创建一个NSTimer,并将它放到当前runloop的default mode中。

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti // 执行任务的时间间隔 
                      target:(id)aTarget // 执行任务的对象
                     selector:(SEL)aSelector // 执行任务的对象方法
                     userInfo:(nullable id)userInfo // 用于传一些参数
                     repeats:(BOOL)yesOrNo; // 是否重复执行

1、怎么保证在未来某个时间点,要执行任务时,target还有效呢?target完全有可能被释放了呀。鉴于此,NSTimer会持有target对象,直到NSTimer invalidate。不同的是,一次性NSTimer会在执行完任务之后,会自动invalidate;而重复性NSTimer,则需要手动invalidate,否则会造成内存泄漏。   

2、由于NSTimer会持有target对象,如果刚好target对象就是self,而NSTimer又是self的一个实例变量,就会引发循环引用:self->NSTimer->self。

@interface TimerTest () {
    NSTimer *_timer; // self持有_timer
}

@end

@implementation TimerTest

- (id)init
{
    if (self = [super init]) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES]; // _timer持有self
    }
    return self;
}

- (void)dealloc
{
    // 因为_timer持有self,所以self的引用计数>=1,也就执行不到dealloc。
    [_timer invalidate]; // 而执行不到dealloc,这句也就执行不到,_timer会一直持有self。这时候只能其他地方将_timer invalidate才行。
}

- (void)test
{

}

@end

为了解决这种循环引用问题,主要有两种方法:

1)实现一个NSTimer的category,并提供block接口。

@implementation NSTimer (Safe)

+ (NSTimer *)safeScheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                        repeats:(BOOL)repeats
                                            blk:(void(^)())blk {
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self // target变成了NSTimer类对象,类对象由系统自动回收
                                       selector:@selector(_doBlock:) // NSTimer会把自己传过去
                                       userInfo:[blk copy] // 把block拷贝到堆上,避免栈block到用时已经被回收了
                                        repeats:repeats];
}

+ (void)_doBlock:(NSTimer *)timer {
    void(^blk)() = timer.userInfo;
    if(blk) {
        blk();
    }
}

@end

@interface TimerTest () {
    NSTimer *_timer; // self持有_timer
}
 
@end
 
@implementation TimerTest
 
- (id)init
{
    if (self = [super init]) {
__weak TimerTest *weakSelf = self; _timer
= [NSTimer safeScheduledTimerWithTimeInterval:1 repeats:YES blk:^{
__strong TimerTest *strongSelf = weakSelf;
[strongSelf test];
}]; } return self; } - (void)dealloc { [_timer invalidate]; } - (void)test { } @end 

3、NSTimer必须放在runloop中才能生效。如下方法就只是创建一个NSTimer,而并没有把它放到runloop中。

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

需要调用如下方法把它放到runloop中,才能生效。 

- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;

 

https://developer.apple.com/documentation/foundation/nstimer/2091889-scheduledtimerwithtimeinterval

posted @ 2018-05-08 20:46  happyyoung  阅读(340)  评论(0编辑  收藏  举报