NSTimer使用注意事项

NSTimer是ios上比较常用的定时器组件,在使用了一段时间后,发现有些地方是需要注意一下的。

  1. NSTimer 是需要配合NSRunLoop 才可以正常工作的。

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

    使用这个类方法,会自动添加到当前的RunLoop里面。关于RunLoop的介绍网上有很多资料,推荐看看 深入理解RunLoop

  2. 当RunLoop处于UITrackingRunLoopMode模式的时候(滑动UIScrollView的时候),使用

    scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
                        invocation:(NSInvocation *)invocation
                           repeats:(BOOL)repeats
    

    的类方法创建的Timer,是不会收到响应事件。只有RunLoop切换到Default模式时才可以正常响应。如果希望滑动时也可以响应Timer时间,需要把Timer加到RunLoop并指定模式为NSRunLoopCommonModes

    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 
                    target:self 
                selector:@selector(test) 
                repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
  3. NSTimer 会强引用 target 对象,很容易造成内存泄露或者其它因生命周期和预期不一至导致的问题。

    我们先看一段常见的事例代码

    @implementation TViewController
    {
        NSTimer *_timer;
    }
    
    - (void)dealloc
    {
        NSLog(@"%s", __func__);
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        _timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:self 
                                                selector:@selector(onTimeout) 
                                                userInfo:nil 
                                                 repeats:YES];
    }
    
    - (void)onTimeout
    {
        NSLog(@"%s", __func__);
    }
    
    @end

    大家可能会觉得,当这个ViewController被 pop 掉后会正常释放,timer 也会停掉。但实际的情况不是你想的那样。以下log是Push这个ViewController后,然后点击返回的过程。

    2016-03-24 00:42:19.663 NSTimerDemo[14916:3982566] -[TViewController onTimeout]
    2016-03-24 00:42:20.663 NSTimerDemo[14916:3982566] -[TViewController onTimeout]
    2016-03-24 00:42:21.663 NSTimerDemo[14916:3982566] -[TViewController onTimeout]
    2016-03-24 00:42:22.663 NSTimerDemo[14916:3982566] -[TViewController onTimeout]
    2016-03-24 00:42:23.369 NSTimerDemo[14916:3982566] -[TViewController viewDidDisappear:]
    2016-03-24 00:42:23.663 NSTimerDemo[14916:3982566] -[TViewController onTimeout]
    2016-03-24 00:42:24.663 NSTimerDemo[14916:3982566] -[TViewController onTimeout]
    2016-03-24 00:42:25.663 NSTimerDemo[14916:3982566] -[TViewController onTimeout]

从日志上来看,dealloc方法确实没有执行,而且timer事件还一直在触发。

OK,既然Timer强引用了ViewController,那把ViewController改成__weak不就是可以解决问题了?
于是我们把创建Timer的代码改成

__weak typeof(self) weak_self = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1                           target:weak_self                                          selector:@selector(onTimeout)
userInfo:nil                                             repeats:YES];

发现输出的log和之前的一样,难道weak对象根本没起作用?
用Instrement查看了一下内存情况,发现真的是Timer强引用Target对象

Timer Retain Target

目前主要是处于一个闭环(环形引用)的状态,我们要想办法打破这种状态,而且__weak设置给Timer也不会破坏Timer强引用Target。

于是,我们引用一个包装对象,让Timer强引用这个包装对象,包装对象弱引用Target(ViewController)
ViewController —> Timer —>Wrapper …>ViewController 这样就可以破坏环形引用。

@Interface Wrapper
@property (weak, nonatom) id target;
@end

那么创建Timer的类方法的Target对象不是传self, 而是传 wrapper 对象。
另外,wrapper对象还要把Timer的事件传递到真正的target上。
详细的 Timer Wrapper 可以看完代码 BSTimer

最后其实可以用dispatch_time解决强引用问题,但是dispatch_time在暂停功能上处理起来比较麻烦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值