NSTimer使用注意的问题

2018-12-27  本文已影响0人  zackwu

总结一下平时使用NSTimer碰到的几个小问题:

NSTimer准确性不高的原因

以上几个问题都跟RunLoop有关系,一个 NSTimer 注册到RunLoop后,RunLoop 会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。

如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。

ScrollView滚动时timer不执行的原因

这里有个概念叫 CommonModes:一个 Mode 可以将自己标记为Common属性(通过将其 ModeName 添加到 RunLoopcommonModes 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 Common 标记的所有Mode里。

应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultModeUITrackingRunLoopMode。这两个 Mode 都已经被标记为Common属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 commonModeItems 中。commonModeItems 被 RunLoop 自动更新到所有具有Common属性的 Mode 里去。

NSTimer导致内存泄漏的原因及解决方法

原因
解决方法

在合适的时机调用invalidate方法。苹果官方的描述如下:

This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.

invaliate是唯一一个将timerNSRunLoop对象移除的方法,如果我们是使用targetuserinfo对象创建的,那么同样会移除对这些对象强引用。

#import "TimerViewController.h"

@interface TimerViewController ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation TimerViewController

- (void)dealloc {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self propertyTimer];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    if ([self.timer isValid]) {
        [self.timer invalidate];
    }
}

#pragma mark - private method

- (void)propertyTimer {
    self.timer = [NSTimer timerWithTimeInterval:5
                                         target:self
                                       selector:@selector(propertyTimerMethod)
                                       userInfo:nil
                                        repeats:YES];
    
    [[NSRunLoop currentRunLoop] addTimer:self.timer
                                 forMode:NSRunLoopCommonModes];
    [self.timer fire];
}

- (void)propertyTimerMethod {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)localTimer {
    //局部变量 无法释放
    NSTimer *timer = [NSTimer timerWithTimeInterval:5
                                             target:self
                                           selector:@selector(localTimerMethod)
                                           userInfo:nil
                                            repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer
                                 forMode:NSRunLoopCommonModes];
    [timer fire];
}

- (void)localTimerMethod {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

参考

深入理解RunLoop
iOS的几种定时器及区别

上一篇 下一篇

猜你喜欢

热点阅读