NSTimer在使用中需要注意的点

NSTimer的常用API
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
  Initializes a timer object with the specified object and selector.
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
  Creates a timer and schedules it on the current run loop in the default mode.

根据官方文档方法一和方法二的区别在于:使用方法一创建的Timer不会添加到NSRunLoop需要手动添加;使用方法二会自动添加到主线程的RunLoop中。

- (void)fire; 
  Causes the timer's message to be sent to its target.
- (void)invalidate;
  Stops the timer from ever firing again and requests its removal from its run loop.

fire方法可以立即触发Timer对象的target方法;invalidate会停止Timer并将其从runloop中移除

NSRunLoop & NSTimer

当使用NSTimerscheduledTimerWithTimeInterval方法时,NSTimer的实例会被加入到当前线程的RunLoop中,模式为默认模式NSDefaultRunLoopMode

- (void)viewDidLoad
{
    [super viewDidLoad];
        
    NSLog(@"主线程 %@", [NSThread currentThread]);
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
    //使用NSRunLoopCommonModes模式,把timer加入到当前Run Loop中。
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)doSomething
{
    NSLog(@"Timer %@", [NSThread currentThread]);
}

控制台输出结果:上下打印出来的线程都是主线程。

如果当你线程是主线程也就是UI线程时,某些UI事件(UIScrollView的滑动操作),会将RunLoop切换到NSEventTrackingRunLoopMode模式。
在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到RunLoop中的Timer就不会执行。这是一个在开发中经常碰到的场景,如何解决这个问题?我们可以在创建完一个Timer后使用NSRunLoopaddTimer:forMode:方法,使用NSRunLoopCommonModes。这是一个占位用的Mode,可以在不同情况下扮演不同的角色——NSDefaultRunLoopMode & UITrackingRunLoopMode

NSTimer循环引用问题

NSTimer为什么会造成循环引用?
在开发中我们经常将Timer作为控制器的属性来使用,这样一来控制器对Timer进行了强引用。在target-action这个过程中,Timer又对self做了强引用,这就是导致循环引用的原因了。在网上有非常多的解决方案,我总结了一下有下面几种。

  • 方法一:在viewWillDisappear中执行下面的操作。

    - (void)viewWillDisappear:(BOOL)animated {
      [super viewWillDisappear:animated];
    
      [_timer invalidate];
      _timer = nil;
    }
    
     -- 
     苹果文档中关于target强引用的解释
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
      Parameters
      ti 
      The number of seconds between firings of the timer. If ti is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead.
      target 
      The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
    

The timer maintains a strong reference to this object until it (the timer) is invalidated.苹果文档说invalidate以后timer就不再保有对target的强引用了。所以解决循环引用的关键在与invalidate方法有没有执行。下面_timer = nil这句话的意义是,invalidate方法执行以后Timer就不能复用了,为了防止在其之后其他地方再次使用Timer,在这里即使将其置为nil。

  • 方法二:引入一个代理对象,让其弱引用self,参考YYWeakProxy
    没引入代理(Proxy)之前是:self -> timer -> self这样的循环引用。在引入代理(Proxy)之后是:self -> timer -> proxy ··> self。这样一来就打破了之前的循环引用。

  • 方法三:使用YYTimerMSWeakTimer等GCD实现的Timer。
    我决定把这一部分内容拿出来单独写一篇对比GCD、CADisplayLink等计时器的总结,除了NSTimer还有两种定时器:CADisplayLink & Dispatch Source Timer欢迎点赞和关注。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 之前要做一个发送短信验证码的倒计时功能,打算用NSTimer来实现,做的过程中发现坑还是有不少的。 基本使用 NS...
    WeiHing阅读 4,408评论 1 8
  • NSTimer是iOS最常用的定时器工具之一,在使用的时候常常会遇到各种各样的问题,最常见的是内存泄漏,通常我们使...
    bomo阅读 1,267评论 0 7
  • 要说软萌之最,这个世界上大概唯有喵星人莫属了吧,撒得了娇,卖的了萌,分分钟成为都市压力之下的治愈系,让主人们心甘情...
    泡面护肤搭档阅读 481评论 0 0
  • 深院静,小庭空, 断续寒砧断续风。 小庭幽院清如水, 冰帘斜卷松针寒。 在繁华喧嚣的都市一角,有这样一处...
    嫣然66阅读 481评论 0 4
  • 起风了,云很少,赶上了北京难得的蓝天,忙碌的心开始闲下来,去细细品尝秋光。 庭院的两棵百年古树在风中依偎摇曳,似在...
    木叶春城阅读 344评论 0 0