NSTimer的使用

NSTimer 的头文件

/*  NSTimer.h
    Copyright (c) 1994-2015, Apple Inc. All rights reserved.
*/

#import <Foundation/NSObject.h>
#import <Foundation/NSDate.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSTimer : NSObject

/**  这下面主要是一些构造方法*/


// Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
// 创建一个定时器,但是么有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

// Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode.
// 创建一个timer并把它指定到一个默认的runloop模式中,并且在 TimeInterval时间后 启动定时器
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;   

// Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
// 创建一个定时器,但是么有添加到运行循环,我们需要在创建定时器后手动的调用 NSRunLoop 对象的 addTimer:forMode: 方法。
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

// Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode.
// 创建一个timer并把它指定到一个默认的runloop模式中,并且在 TimeInterval时间后 启动定时器
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

// 默认的初始化方法,(创建定时器后,手动添加到 运行循环,并且手动触发才会启动定时器)
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;


// You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.   
// 启动 Timer 触发Target的方法调用但是并不会改变Timer的时间设置。 即 time没有到达到,Timer会立即启动调用方法且没有改变时间设置,当时间 time 到了的时候,Timer还是会调用方法。
- (void)fire;

// 这是设置定时器的启动时间,常用来管理定时器的启动与停止
@property (copy) NSDate *fireDate;
      // 启动定时器 
          timer.fireDate = [NSDate distantPast];    
      //停止定时器 
          timer.fireDate = [NSDate distantFuture];
      // 开启 
         [time setFireDate:[NSDate  distanPast]]
      // NSTimer   关闭  
        [time  setFireDate:[NSDate  distantFunture]]
      //继续。
        [timer setFireDate:[NSDate date]]; 
      

// 这个是一个只读属性,获取定时器调用间隔时间
@property (readonly) NSTimeInterval timeInterval;

// Setting a tolerance for a timer allows it to fire later than the scheduled fire date, improving the ability of the system to optimize for increased power savings and responsiveness. The timer may fire at any time between its scheduled fire date and the scheduled fire date plus the tolerance. The timer will not fire before the scheduled fire date. For repeating timers, the next fire date is calculated from the original fire date regardless of tolerance applied at individual fire times, to avoid drift. The default value is zero, which means no additional tolerance is applied. The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of this property.
// As the user of the timer, you will have the best idea of what an appropriate tolerance for a timer may be. A general rule of thumb, though, is to set the tolerance to at least 10% of the interval, for a repeating timer. Even a small amount of tolerance will have a significant positive impact on the power usage of your application. The system may put a maximum value of the tolerance.

// 这是7.0之后新增的一个属性,因为NSTimer并不完全精准,通过这个值设置误差范围
@property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0);

// 停止 Timer ---> 唯一的方法将定时器从循环池中移除
- (void)invalidate;

// 获取定时器是否有效
@property (readonly, getter=isValid) BOOL valid;

// 获取参数信息---> 通常传入的是 nil
@property (nullable, readonly, retain) id userInfo;

@end

NS_ASSUME_NONNULL_END

注意:这五种初始化方法的异同:

    1、参数repeats是指定是否循环执行,YES将循环,NO将只执行一次。
    2、timerWithTimeInterval  这两个类方法创建出来的对象如果不用 addTimer: forMode方法手动加入主循环池中,将不会循环执行。
    3、scheduledTimerWithTimeInterval  这两个方法会将定时器添加到当前的运行循环,运行循环的模式为默认模式。
    4、init方法需要手动加入循环池,它会在设定的启动时间启动。

NSTimer 使用过程中的问题:
1、 内存释放问题
如果我们启动了一个定时器,在某个界面释放前,将这个定时器停止,甚至置为nil,都不能使这个界面释放,原因是系统的循环池中还保有这个对象。
(** timer都会对它的target进行retain,我们需要小心对待这个target的生命周期问题,尤其是重复性的timer**)

所以我们需要这样做:

-(void)dealloc{
       NSLog(@"dealloc:%@",[self class]);
}

- (void)viewDidLoad {
      [super viewDidLoad];
      timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myLog:) userInfo:nil repeats:YES];
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
      btn.backgroundColor=[UIColor redColor];
      [btn addTarget:self action:@selector(btn) forControlEvents:UIControlEventTouchUpInside];
      [self.view addSubview:btn];
}

-(void)btn{    
      if (timer.isValid) {       
          [timer invalidate];  // 从运行循环中移除, 对运行循环的引用进行一次 release
          timer=nil;            // 将销毁定时器
      }
      
      [self dismissViewControllerAnimated:YES completion:nil];
}

NSTimer为什么要添加到RunLoop中才会有作用

便利构造器,它其实是做了两件事:
首先创建一个timer,然后将该timer添加到当前runloop的default mode中。

也就是这个便利方法给我们造成了只要创建了timer就可以生效的错觉,我们当然可以自己创建timer,然后手动的把它添加到指定runloop的指定mode中去。

NSTimer其实也是一种资源(事件),如果看过多线程变成指引文档的话,我们会发现所有的source(事件)如果要起作用,就得加到runloop中去。
同理timer这种资源要想起作用,那肯定也需要加到runloop中才会有效喽。
如果一个runloop里面不包含任何资源(事件)的话,运行该runloop时会处于一种休眠状态等待下一个事件。

没有将事件添加到运行循环中

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [self testTimerWithOutShedule];
}

- (void)testTimerWithOutShedule
{
    NSLog(@"Test timer without shedult to runloop");
    SvTestObject *testObject3 = [[SvTestObject alloc] init];
    NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject3 selector:@selector(timerAction:) userInfo:nil repeats:NO];
    
    NSLog(@"invoke release to testObject3");
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    NSLog(@"SvTimerSample Will resign Avtive!");
}

我们新建了一个timer,为它指定了有效的target和selector,并指出了1秒后触发该消息,运行结果如下:


Snip20151212_4.png

消息永远也不会触发,原因很简单,我们没有将timer添加到runloop中。
  综上: 必须得把timer添加到runloop中,它才会生效。

NSTimer加到了RunLoop中但迟迟的不触发事件

原因主要有以下两个:
1、runloop是否运行
每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动。

- (void)applicationDidBecomeActive:(UIApplication *)application
{
     // NSThread 创建一个子线程
    [NSThread  detachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:self withObject:nil];
}

// 测试把timer加到不运行的runloop上的情况
- (void)testTimerSheduleToRunloop1
{   
    NSLog(@"Test timer shedult to a non-running runloop");
    SvTestObject *testObject4 = [[SvTestObject alloc] init];
    NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject4 selector:@selector(timerAction:) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 打开下面一行输出runloop的内容就可以看出,timer却是已经被添加进去
    //NSLog(@"the thread's runloop: %@", [NSRunLoop currentRunLoop]);
    
    // 打开下面一行, 该线程的runloop就会运行起来,timer才会起作用
    //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];

    NSLog(@"invoke release to testObject4");
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    NSLog(@"SvTimerSample Will resign Avtive!");
}

我们新创建了一个线程,然后创建一个timer,并把它添加当该线程的runloop当中,但是运行结果如下:

Snip20151212_5.png

发现这个timer知道执行退出也没有触发我们指定的方法,如果我们把上面测试程序中“

//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];

这一行的注释去掉,则timer将会正确的掉用我们指定的方法。

2、mode是否正确
手动添加runloop的时候,可以看到有一个参数runloopMode,这个参数是干嘛的呢?
  前面提到了要想timer生效,我们就得把它添加到指定runloop的指定mode中去,通常是主线程的defalut mode。但有时我们这样做了,却仍然发现timer还是没有触发事件。
这是因为timer添加的时候,我们需要指定一个mode,因为同一线程的runloop在运行的时候,任意时刻只能处于一种mode。所以只能当程序处于这种mode的时候,timer才能得到触发事件的机会。

综上: 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。

NSTimer 的使用

//不重复,只调用一次。timer运行一次就会自动停止运行 
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(scrollTimer) userInfo:nil repeats:NO];  
 

需要重复调用, repeats参数改为 YES . ---> 定时器的模式是默认的
//每1秒运行一次function方法。  
timer =  [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];  

注意点:
将计数器的repeats设置为YES的时候,self的引用计数会加1。
因此可能会导致self(即viewController)不能release。
所以,必须在viewWillAppear的时候,将计数器timer停止,否则可能会导致内存泄露。


//取消定时器  
[timer invalidate];   // 将定时器从运行循环中移除,
timer = nil;    // 销毁定时器 ---》 这样可以避免控制器不死


要想实现:先停止,然后再某种情况下再次开启运行timer,可以使用下面的方法:
首先关闭定时器不能使用上面的方法,应该使用下面的方法:
//关闭定时器  
[myTimer setFireDate:[NSDate distantFuture]];

然后就可以使用下面的方法再此开启这个timer了:
//开启定时器  
[myTimer setFireDate:[NSDate distantPast]];  


例子:比如,在页面消失的时候关闭定时器,然后等页面再次打开的时候,又开启定时器。
(主要是为了防止它在后台运行,暂用CPU)可以使用下面的代码实现:

//页面将要进入前台,开启定时器  
-(void)viewWillAppear:(BOOL)animated  
{  
    //开启定时器  
    [scrollView.myTimer setFireDate:[NSDate distantPast]];  
}  
  
//页面消失,进入后台不显示该页面,关闭定时器  
-(void)viewDidDisappear:(BOOL)animated  
{  
    //关闭定时器  
    [scrollView.myTimer setFireDate:[NSDate distantFuture]];  
}  

注意点:
[timer invalidate]是唯一的方法将定时器从循环池中移除
NSTimer可以精确到50-100毫秒.
NSTimeInterval类:是一个浮点数字,用来定义秒
NSTimer不是绝对准确的,而且中间耗时或阻塞错过下一个点,那么下一个点就pass过去了.

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

推荐阅读更多精彩内容