NSTimer

  1. 创建timer的方式
#import "TimerViewController1.h"

@interface TimerViewController1 ()

@end

@implementation TimerViewController1

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // 必须手动添加到runloop才会启动
    // 首次回调时间为1秒后
    NSTimer *timer1 = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timer1Event) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer1 forMode:NSRunLoopCommonModes];
    
    // 自动添加到runloop,自动启动
    // 首次回调时间为1秒后
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timer2Event) userInfo:nil repeats:YES];
    
    // 需要手动添加到runloop启动定时器,否则使用[timer3 fire]启动的话,只会回调一次
    // 首次回调时间为设置的FireDate
    NSTimer *timer3 = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(timer3Event) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer3 forMode:NSRunLoopCommonModes];
}

- (void)timer1Event {
    NSLog(@"%s", __FUNCTION__);
}

- (void)timer2Event {
    NSLog(@"%s", __FUNCTION__);
}

- (void)timer3Event {
    NSLog(@"%s", __FUNCTION__);
}

@end

上述3种方式都存在内存泄漏的问题

  1. 循环引用和内存泄漏的分析

一般的话,我们创建一个定时器持有关系如下:


循环引用

那我把target对象对 NSTimer 变为弱引用不就解决了循环持有的问题了吗? 如下:


打破循环引用

即使这样,target依然不能释放,分析如下:
runloop持有target

主线程的Runloop在程序运行期间是不会销毁的,它比self的生命周期都长,也就是runloop引用着timer,timer就不会销毁,timer引用着target,target也不会销毁。runloop间距持有了target。

  1. 中间对象解决循环引用

可以使用一个中间对象,对 NSTimer 和 target进行弱引用,这样就解决了。当 VC 销毁了,target 也会销毁,可以通过中间对象来判断 target 是否为 nil。这样的话就可以把 NSTimer 置为无效和 nil,这样也就打破了循环引用的目的了。


解决方案
// 中间对象
@interface WeakTimer : NSObject

+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

- (instancetype)initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

@end

@interface WeakTimer ()

@property (nonatomic, weak) id aTarget;
@property (nonatomic, weak) NSTimer *timer;
@property (nonatomic, assign) SEL aSelector;

@end

@implementation WeakTimer

+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
    return [[self alloc] initWithTimeInterval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo];
}

- (instancetype)initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
    self = [super init];
    if (self) {
        _aTarget = aTarget;
        _aSelector = aSelector;
        _timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(fire) userInfo:userInfo repeats:yesOrNo];
    }
    return self;
}

- (void)dealloc {
    NSLog(@"WeakTimer dealloc");
}

- (void)fire {
    if (_aTarget) {
        [_aTarget performSelector:_aSelector];
    } else {
        [_timer invalidate];
        _timer = nil;
    }
}

@end
// 使用
@interface TimerViewController ()

@property (nonatomic, weak) WeakTimer *timer;

@end

@implementation TimerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = UIColor.whiteColor;
    
    _timer = [WeakTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(callBack) userInfo:nil repeats:YES];
}

- (void)dealloc {
    NSLog(@"TimerViewController dealloc");
}

- (void)callBack {
    NSLog(@"callBack");
}

@end

在iOS 10以后系统,苹果针对NSTimer进行了优化,使用Block回调方式,解决了循环引用问题。

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"打印了");
}];

- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
     NSLog(@"** dealloc **");
}

使用这种系统的 api方法,会执行的dealloc,只要在dealloc里面进行相关取消定时器的操作就就可以了。

还有下面一种方式,这种适合 push的页面,不适应present的。

//生命周期  移除VC的时候,这种适合 push的页面,不适应Present
- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (parent == nil) {
        [self.timer invalidate];
        self.timer = nil;
    }
}
  1. NSTimer未启动原因分析

如果当前线程是主线程的话,某些UI事件,比如UIScrollView的拖拽操作,会将Runloop切换成UITrackingRunLoopMode,这时候,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。所以为了设置一个不会被UI干扰的Timer,我们需要手动将timer的当前RunloopMode设置为NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode和UITrackingRunLoopMode的结合。

  1. 注意点
  • NSTimer 最常用,需要注意的就是加入的 runLoop 的 Mode ,若是子线程,需要手动 run 这个 RunLoop ;同时注意使用 invalidate 手动停止定时,否则引起内存泄漏;NSTimer的创建与撤销必须在同一个线程操作,不能跨越线程操作;
  • GCD Timer 较 NSTimer 精度高,一般用于对文件资源等定期读写操作很方便,使用时需要注意 dispatch_resume 与 dispatch_suspend 配套,并且要给 dispatch source 设置新值或者置nil,需先 dispatch_source_cancel(timer) ,否则会导致崩溃;
  • 需与显示更新同步的定时,建议 CADisplayLink ,可以省去多余计算;
  • iOS中任何定时器的精度,都只是个参考值。

iOS 中精确定时的常用方法
NSTimer 的正确用法你真的知道吗?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容