看到这个标题,你可能会想NSTimer不就是计时器吗,谁不会用,不就是一个能够定时的完成任务的东西吗?

  我想说你知道NSTimer会retain你添加调用方法的对象吗?你知道NSTimer是要加到runloop中才会起作用吗?你知道NSTimer会并不是准确的按照你指定的时间触发的吗?你知道NSTimer就算添加到runloop了也不一定会按照你想象中的那样执行吗?

  如果上面提出的哪些问题,你并不全部了解,那么请细心的看完下面的文章,上面的那几个问题我会一一说明,并给出详细的例子。

一、什么是NSTimer

  官方给出解释是“A timer provides a way to perform a delayed action or a periodic action. The timer waits until a certain time interval has elapsed and then fires, sending a specified message to a specified object. ” 翻译过来就是timer就是一个能在从现在开始的后面的某一个时刻或者周期性的执行我们指定的方法的对象。

二、NSTimer和它调用的函数对象间到底发生了什么

  从前面官方给出的解释可以看出timer会在未来的某个时刻执行一次或者多次我们指定的方法,这也就牵扯出一个问题,如何保证timer在未来的某个时刻触发指定事件的时候,我们指定的方法是有效的呢?

  解决方法很简单,只要将指定给timer的方法的接收者retain一份就搞定了,实际上系统也是这样做的。不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显示的invalidate它为止”。

  下面我们看个小例子:

[cpp] view plain copy
  1. //
  2. //  TestObject.h
  3. //  NSTimerDemo
  4. //
  5. //  Created by administrator on 13-6-24.
  6. //  Copyright (c) 2013年 enuola. All rights reserved.
  7. //
  8. #import <Foundation/Foundation.h>
  9. @interface TestObject : NSObject
  10. //timer响应函数,只是用来做测试
  11. -(void)timerAction:(NSTimer *)timer;
  12. @end
[cpp] view plain copy
  1. //
  2. //  TestObject.m
  3. //  NSTimerDemo
  4. //
  5. //  Created by administrator on 13-6-24.
  6. //  Copyright (c) 2013年 enuola. All rights reserved.
  7. //
  8. #import "TestObject.h"
  9. @implementation TestObject
  10. -(id)init
  11. {
  12. self = [super init];
  13. if(self)
  14. {
  15. NSLog(@"instance %@ has been created!", self);
  16. }
  17. return self;
  18. }
  19. -(void)dealloc
  20. {
  21. NSLog(@"instance %@ has been dealloced!", self);
  22. [super dealloc];
  23. }
  24. -(void)timerAction:(NSTimer *)timer
  25. {
  26. NSLog(@"Hi,Timer Action for instance %@", self);
  27. }
  28. @end
[cpp] view plain copy
  1. //调用上面定义的类,进行测试
  2. //  ViewController.m
  3. //  NSTimerDemo
  4. //
  5. //  Created by administrator on 13-6-24.
  6. //  Copyright (c) 2013年 enuola. All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. #import "TestObject.h"
  10. @interface ViewController ()
  11. @end
  12. @implementation ViewController
  13. - (void)viewDidLoad
  14. {
  15. [super viewDidLoad];
  16. // Do any additional setup after loading the view, typically from a nib.
  17. //测试NSTimer是否会retain一个Object对象?答案:会
  18. [self testNonRepeatTimer];
  19. //    [self testRepeatTimer];
  20. }
  21. //不循环的定时器测试方法
  22. -(void)testNonRepeatTimer
  23. {
  24. NSLog(@"Test retain target for non-repeat");
  25. TestObject *testObject = [[TestObject alloc] init];
  26. [NSTimer scheduledTimerWithTimeInterval:5 target:testObject selector:@selector(timerAction:) userInfo:nil repeats:NO];
  27. [testObject release];
  28. NSLog(@"Invoke release to testObject");
  29. }
  30. //循环的定时器测试方法
  31. -(void)testRepeatTimer
  32. {
  33. NSLog(@"Test retain target for repeat Timer");
  34. TestObject *testObject2 = [[TestObject alloc] init];
  35. [NSTimer scheduledTimerWithTimeInterval:5 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES];
  36. [testObject2 release];
  37. NSLog(@"Invoke release to testObject2");
  38. }

上面的简单例子中,我们自定义了一个继承自NSObject的类TestObject,在这个类的init,dealloc和它的timerAction三个方法中分别打印信息。然后在appDelegate中分别测试一个单次执行的timer和一个重复执行的timer对方法接受者是否做了retain操作,因此我们在两种情况下都是shedule完timer之后立马对该测试对象执行release操作。

  测试单次执行的timer的结果如下:

    观察输出,我们会发现53分58秒的时候我们就对测试对象执行了release操作,但是知道54分03秒的时候timer触发完方法以后,该对象才实际的执行了dealloc方法。这就证明一次性的timer也会retain它的方法接收者,直到自己失效为之。

  测试重复性的timer的结果如下:

观察输出我们发现,这个重复性的timer一直都在周期性的调用我们为它指定的方法,而且测试的对象也一直没有真正的被释放。

  通过以上小例子,我们可以发现在timer对它的接收者进行retain,从而保证了timer调用时的正确性,但是又引入了接收者的内存管理问题。特别是对于重复性的timer,它所引用的对象将一直存在,将会造成内存泄露。

  有问题就有应对方法,NSTimer提供了一个方法invalidate,让我们可以解决这种问题。不管是一次性的还是重复性的timer,在执行完invalidate以后都会变成无效,因此对于重复性的timer我们一定要有对应的invalidate。

因此你有必要看一下这篇文章咯: IOS中定时器NSTimer的开启与关闭

   综上: timer都会对它的target进行retain,我们需要小心对待这个target的生命周期问题,尤其是重复性的timer。

三、NSTimer会是准时触发事件吗

  答案是否定的,而且有时候你会发现实际的触发时间跟你想象的差距还比较大。NSTimer不是一个实时系统,因此不管是一次性的还是周期性的,timer的实际触发事件的时间可能都会跟我们预想的会有出入。差距的大小跟当前我们程序的执行情况有关系,比如可能程序是多线程的,而你的timer只是添加在某一个线程的runloop的某一种指定的runloopmode中,由于多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化。

  假设你添加了一个timer指定2秒后触发某一个事件,但是签好那个时候当前线程在执行一个连续运算(例如大数据块的处理等),这个时候timer就会延迟到该连续运算执行完以后才会执行。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会和后面的触发进行合并,即在一个周期内只会触发一次。但是不管该timer的触发时间延迟的有多离谱,他后面的timer的触发时间总是倍数于第一次添加timer的间隙。

  原文如下“A repeating timer reschedules itself based on the scheduled firing time, not the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.”

  下面请看一个简单的例子:

[cpp] view plain copy
  1. //
  2. //  ViewController.m
  3. //  NSTimerDemo
  4. //
  5. //  Created by administrator on 13-6-24.
  6. //  Copyright (c) 2013年 enuola. All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. #import "TestObject.h"
  10. @interface ViewController ()
  11. @end
  12. @implementation ViewController
  13. - (void)viewDidLoad
  14. {
  15. [super viewDidLoad];
  16. // Do any additional setup after loading the view, typically from a nib.
  17. //测试NSTimer是否会准时触发?答案:不会
  18. TestObject *testObject = [[TestObject alloc] init];
  19. [NSTimer scheduledTimerWithTimeInterval:1 target:testObject selector:@selector(timerAction:) userInfo:nil repeats:YES];
  20. [testObject release];
  21. NSLog(@"simulate busy");
  22. [self performSelector:@selector(simulateBusy) withObject:nil afterDelay:5];
  23. }
  24. //模拟当前线程正好繁忙的情况
  25. -(void)simulateBusy
  26. {
  27. NSLog(@"star simulate busy");
  28. NSUInteger caculateCount = 0x0FFFFFFF;
  29. CGFloat uselessValue = 0;
  30. for (NSUInteger i = 0; i < caculateCount; ++i)
  31. {
  32. uselessValue = i /0.333;
  33. }
  34. NSLog(@"finish simulate busy");
  35. }
  36. -(void)testTimerWithOutShedule
  37. {
  38. NSLog(@"Test timer without shedult to runloop");
  39. TestObject *testObject3 = [[TestObject alloc] init];
  40. NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject3 selector:@selector(timerAction:) userInfo:nil repeats:NO];
  41. [testObject3 release];
  42. NSLog(@"invoke release to testObject3");
  43. }

例子中首先开启了一个timer,这个timer每隔1秒调用一次target的timerAction方法,紧接着我们在3秒后调用了一个模拟线程繁忙的方法(其实就是一个大的循环)。运行程序后输出结果如下:

仔细观察可以发现:当线程空闲的时候timer的消息触发还是比较准确的,但是在20分51秒开始线程一直忙着做大量运算,知道20分53秒该运算才结束,这个时候timer才触发消息,这个线程繁忙的过程超过了一个周期,但是timer并没有连着触发两次消息,而只是触发了一次。等线程忙完以后后面的消息触发的时间仍然都是整数倍与开始我们指定的时间,这也从侧面证明,timer并不会因为触发延迟而导致后面的触发时间发生延迟。

  综上: timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。

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

  前面的例子中我们使用的是一种便利方法,它其实是做了两件事:首先创建一个timer,然后将该timer添加到当前runloop的default mode中。也就是这个便利方法给我们造成了只要创建了timer就可以生效的错觉,我们当然可以自己创建timer,然后手动的把它添加到指定runloop的指定mode中去。

  NSTimer其实也是一种资源,如果看过多线程编程指引文档的话,我们会发现所有的source如果要起作用,就得加到runloop中去。同理timer这种资源要想起作用,那肯定也需要加到runloop中才会生效喽。如果一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。你可能会说那我们APP的主线程的runloop我们没有往其中添加任何资源,为什么它还好好的运行。我们不添加,不代表框架没有添加,如果有兴趣的话你可以打印一下main thread的runloop,你会发现有很多资源。

  下面我们看一个小例子:

[cpp] view plain copy
  1. //
  2. //  ViewController.m
  3. //  NSTimerDemo
  4. //
  5. //  Created by administrator on 13-6-24.
  6. //  Copyright (c) 2013年 enuola. All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. #import "TestObject.h"
  10. @interface ViewController ()
  11. @end
  12. @implementation ViewController
  13. - (void)viewDidLoad
  14. {
  15. [super viewDidLoad];
  16. // Do any additional setup after loading the view, typically from a nib.
  17. //测试NSTimer是否需要添加到Runloop中才有效?答案:是的
  18. [self testTimerWithOutShedule];
  19. }
  20. -(void)testTimerWithOutShedule
  21. {
  22. NSLog(@"Test timer without shedult to runloop");
  23. TestObject *testObject3 = [[TestObject alloc] init];
  24. NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject3 selector:@selector(timerAction:) userInfo:nil repeats:NO];
  25. [testObject3 release];
  26. NSLog(@"invoke release to testObject3");
  27. }
  28. - (void)didReceiveMemoryWarning
  29. {
  30. [super didReceiveMemoryWarning];
  31. // Dispose of any resources that can be recreated.
  32. }
  33. @end

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

观察发现这个消息永远也不会触发,原因很简单,我们没有将timer添加到runloop中。

  综上: 必须得把timer添加到runloop中,它才会生效。

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

  为什么明明添加了,但是就是不按照预先的逻辑触发事件呢???原因主要有以下两个:

1、runloop是否运行

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

  那么如果我们把一个timer添加到了非主线的runloop中,它还会按照预期按时触发吗?下面请看一段测试程序:

[cpp] view plain copy
  1. //
  2. //  ViewController.m
  3. //  NSTimerDemo
  4. //
  5. //  Created by administrator on 13-6-24.
  6. //  Copyright (c) 2013年 enuola. All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. #import "TestObject.h"
  10. @interface ViewController ()
  11. @end
  12. @implementation ViewController
  13. - (void)viewDidLoad
  14. {
  15. [super viewDidLoad];
  16. // Do any additional setup after loading the view, typically from a nib.
  17. //另开一个新的线程,进行测试,而非主线程。
  18. [NSThread detachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:self withObject:nil];
  19. }
  20. //测试把timer加到不运行的runloop上的情况
  21. -(void)testTimerSheduleToRunloop1
  22. {
  23. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  24. NSLog(@"Test timer shedult to a non-running runloop");
  25. TestObject *testObject4 = [[TestObject alloc] init];
  26. NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject4 selector:@selector(timerAction:) userInfo:nil repeats:NO];
  27. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  28. //打开下面一行输出runloop的内容就可以看出,timer确实已经被添加进去
  29. //    NSLog(@"the thread's runloop: %@", [NSRunLoop currentRunLoop]);
  30. //打开下面一行,该线程的runloop就会运行起来,timer才会起作用
  31. //    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
  32. [testObject4 release];
  33. NSLog(@"invoke release to testObject4");
  34. [pool release];
  35. }
  36. - (void)didReceiveMemoryWarning
  37. {
  38. [super didReceiveMemoryWarning];
  39. // Dispose of any resources that can be recreated.
  40. }
  41. @end

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

其中,第一行注释的代码,可以打印输出该线程的Runloop中确实已经有了timer资源。观察运行结果,我们发现这个timer知道执行退出也没有触发我们指定的方法,如果我们把上面测试程序中:

[cpp] view plain copy
  1. //    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];

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

2、mode是否正确

  我们前面自己动手添加runloop的时候,可以看到有一个参数runloopMode,这个参数是干嘛的呢?

  前面提到了要想timer生效,我们就得把它添加到指定runloop的指定mode中去,通常是主线程的defalut mode。但有时我们这样做了,却仍然发现timer还是没有触发事件。这是为什么呢?

  这是因为timer添加的时候,我们需要指定一个mode,因为同一线程的runloop在运行的时候,任意时刻只能处于一种mode。所以只能当程序处于这种mode的时候,timer才能得到触发事件的机会。

  举个不恰当的例子,我们说兄弟几个分别代表runloop的mode,timer代表他们自己的水桶,然后一群人去排队打水,只有一个水龙头,那么同一时刻,肯定只能有一个人处于接水的状态。也就是说你虽然给了老二一个桶,但是还没轮到它,那么你就得等,只有轮到他的时候你的水桶才能派上用场。

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

iOS中关于NSTimer使用知多少相关推荐

  1. iOS中定时器NSTimer的使用

    1.初始化 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelect ...

  2. iOS中定时器NSTimer的开启与关闭

    调用一次计时器方法: [cpp] view plain copy   myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self ...

  3. IOS中定时器NSTimer

    调用一次计时器方法: [cpp]  view plain copy   myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:sel ...

  4. iOS中的异步和多线程概况

    一.异步 (1)当一个异步过程调用发出后,调用者不能立刻得到结果.实际处理这个调用的部件在完成后,通过状态.通知和回调来通知调用者.比如iOS类库中的NSURLConnectioin中使用代理的方式就 ...

  5. iOS中几种数据持久化方案总结

    概论 所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据.在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案: plist文件(属性列表) ...

  6. iOS中的CADisplayLink定时器

    2019独角兽企业重金招聘Python工程师标准>>> iOS中的CADisplayLink定时器 说到定时器,在iOS中最常用的为NSTimer类,其实CADisplayLink类 ...

  7. IOS中获取各个文件的目录路径的方法和NSFileManager类

    转自:http://blog.sina.com.cn/s/blog_5fb39f910101di92.html IOS中获取各种文件的目录路径的方法 iphone沙箱模型的有四个文件夹,分别是什么,永 ...

  8. [转] iOS中@class #import #include 简介

    [转载自:http://blog.csdn.net/chengwuli125/article/details/9705315] 一.解析        很多刚开始学习iOS开发的同学可能在看别人的代码 ...

  9. iOS中本地化字符串

    2019独角兽企业重金招聘Python工程师标准>>> iOS中本地化字符串 04月18日Array428 编辑/纠错 本地化字符串最常用的工具是NSLocalizedString. ...

最新文章

  1. python 读取excel文件 效率 时间 格式_python读取Excel文件中的时间数据
  2. [转]Web测试中的界面测试用例设计
  3. 3.2 使用pytorch搭建AlexNet并训练花分类数据集
  4. python转义是什么意思_Python什么情况下会输出转义符
  5. 潍坊学院计算机系崔玲玲,人工免疫算法在引水工程中的应用.pdf
  6. mysql输出重定向_将MySQL输出内容写入(重定向到)文件
  7. java和python哪个运行速度快_为什么Python代码的运行速度比较慢呢?这会影响Python语言的扩张吗?...
  8. [源码和报告分享]基于Android-JavaEE-DB2实现的旧物交易平台
  9. 【BZOJ3991】【SDOI2015】寻宝游戏
  10. 如何把晨光计算机调成音乐模式,伴着晨光走向你——广播《晨光音乐行》栏目运作心得...
  11. 对未来人工智能的一些预测和想法
  12. 微信小程序----全局状态管理 (便于全局埋点等操作)
  13. 太极自定义diy名片模板_没有合适的手帐本?拿走这些电子模板,自制属于自己的手帐本...
  14. 腾讯员工的1则匿名帖子,让我细思极恐:不要低估人性的恶
  15. ssh远程连接报错:WARNING: POSSIBLE DNS SPOOFING DETECTED(已解决)
  16. python求最小公倍数_Python实现的求解最小公倍数算法示例
  17. 基于seq2seq的中国古诗词自动生成技术
  18. 电脑箭头,电脑箭头符号怎么打出来(往返箭头符号图案)
  19. 用于移动端的相关网址
  20. Red Herring2010年亚洲区创新实用产品排名

热门文章

  1. java表驱动法索引访问_表驱动法 - SegmentFault 思否
  2. Python使用matplotlib可视化Treemap图、treemap将分层数据显示为一组嵌套矩形,每一组都用一个矩形表示,该矩形的面积与其值成正比(Treemap)
  3. python使用matplotlib可视化subplots子图、subplots绘制子图、子图之间有重叠问题、使用subplots_adjust函数合理设置子图之间的水平和垂直距离
  4. 多分类问题的ROC曲线绘制
  5. R可视化散点图并绘制回归曲线
  6. 特征工程之数据分箱、Nominal特征编码、Ordinal分类特征编码、特征交叉组合、特征差分
  7. 时间的玫瑰+但斌的投资思维
  8. KNN(K Nearest Neighbors)分类是什么学习方法?如何或者最佳的K值?RadiusneighborsClassifer分类器又是什么?KNN进行分类详解及实践
  9. 单分子测序技术取得重要突破
  10. android alpha不起作用,API 28(P)的Android设计支持库不起作用