详细连接 : http://justsee.iteye.com/blog/1774722多线程
看到这个标题,你可能会想NSTimer不就是计时器吗,谁不会用,不就是一个可以定时的完成任务的东西吗?app
我想说你知道NSTimer会retain你添加调用方法的对象吗?你知道NSTimer是要加到runloop中才会起做用吗?你知道NSTimer会并非准确的按照你指定的时间触发的吗?你知道NSTimer就算添加到runloop了也不必定会按照你想象中的那样执行吗?框架
若是上面提出的哪些问题,你并不所有了解,那么请细心的看完下面的文章,上面的那几个问题我会一一说明,并给出详细的例子。less
1、什么是NSTimeride
官方给出解释是“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就是一个能在从如今开始的后面的某一个时刻或者周期性的执行咱们指定的方法的对象。函数
2、NSTimer和它调用的函数对象间到底发生了什么oop
从前面官方给出的解释能够看出timer会在将来的某个时刻执行一次或者屡次咱们指定的方法,这也就牵扯出一个问题,如何保证timer在将来的某个时刻触发指定事件的时候,咱们指定的方法是有效的呢?测试
解决方法很简单,只要将指定给timer的方法的接收者retain一份就搞定了,实际上系统也是这样作的。不论是重复性的timer仍是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用之后会自动将本身invalidate,而重复的timer则将永生,直到你显示的invalidate它为止”。大数据
下面咱们看个小例子:ui
// // SvTestObject.m // SvTimerSample // // Created by maple on 12/19/12. // Copyright (c) 2012 maple. All rights reserved. // #import "SvTestObject.h" @implementation SvTestObject - (id)init { self = [super init]; if (self) { NSLog(@"instance %@ has been created!", self); } return self; } - (void)dealloc { NSLog(@"instance %@ has been dealloced!", self); [super dealloc]; } - (void)timerAction:(NSTimer*)timer { NSLog(@"Hi, Timer Action for instance %@", self); } @end
// // SvTestObject.h // SvTimerSample // // Created by maple on 12/19/12. // Copyright (c) 2012 maple. All rights reserved. // #import <Foundation/Foundation.h> @interface SvTestObject : NSObject /* * @brief timer响应函数,只是用来作测试 */ - (void)timerAction:(NSTimer*)timer; @end
- (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. // test Timer retain target [self testNonRepeatTimer]; // [self testRepeatTimer]; } - (void)testNonRepeatTimer { NSLog(@"Test retatin target for non-repeat timer!"); SvTestObject *testObject = [[SvTestObject alloc] init]; [NSTimer scheduledTimerWithTimeInterval:5 target:testObject selector:@selector(timerAction:) userInfo:nil repeats:NO]; [testObject release]; NSLog(@"Invoke release to testObject!"); } - (void)testRepeatTimer { NSLog(@"Test retain target for repeat Timer"); SvTestObject *testObject2 = [[SvTestObject alloc] init]; [NSTimer scheduledTimerWithTimeInterval:5 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES]; [testObject2 release]; NSLog(@"Invoke release to testObject2!"); }
上面的简单例子中,咱们自定义了一个继承自NSObject的类SvTestObject,在这个类的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。
忽然想起一种自欺欺人的写法,不知道大家有没有这么写过,我认可以前也有这样写过,哈哈,代码以下:
// // SvCheatYourself.m // SvTimerSample // // Created by maple on 12/19/12. // Copyright (c) 2012 maple. All rights reserved. // // 如下这种timer的用法,企图在dealloc中对timer进行invalidate是一种自欺欺人的作法 // 由于你的timer对self进行了retain,若是timer一直有效,则self的引用计数永远不会等于0 #import "SvCheatYourself.h" @interface SvCheatYourself () { NSTimer *_timer; } @end @implementation SvCheatYourself - (id)init { self = [super init]; if (self) { _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTimer:) userInfo:nil repeats:YES]; } return self; } - (void)dealloc { // 自欺欺人的写法,永远都不会执行到,除非你在外部手动invalidate这个timer [_timer invalidate]; [super dealloc]; } - (void)testTimer:(NSTimer*)timer { NSLog(@"haha!"); } @end// // SvCheatYourself.m // SvTimerSample // // Created by maple on 12/19/12. // Copyright (c) 2012 maple. All rights reserved. // // 如下这种timer的用法,企图在dealloc中对timer进行invalidate是一种自欺欺人的作法 // 由于你的timer对self进行了retain,若是timer一直有效,则self的引用计数永远不会等于0 #import "SvCheatYourself.h" @interface SvCheatYourself () { NSTimer *_timer; } @end @implementation SvCheatYourself - (id)init { self = [super init]; if (self) { _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTimer:) userInfo:nil repeats:YES]; } return self; } - (void)dealloc { // 自欺欺人的写法,永远都不会执行到,除非你在外部手动invalidate这个timer [_timer invalidate]; [super dealloc]; } - (void)testTimer:(NSTimer*)timer { NSLog(@"haha!"); } @end// // SvCheatYourself.m // SvTimerSample // // Created by maple on 12/19/12. // Copyright (c) 2012 maple. All rights reserved. // // 如下这种timer的用法,企图在dealloc中对timer进行invalidate是一种自欺欺人的作法 // 由于你的timer对self进行了retain,若是timer一直有效,则self的引用计数永远不会等于0 #import "SvCheatYourself.h" @interface SvCheatYourself () { NSTimer *_timer; } @end @implementation SvCheatYourself - (id)init { self = [super init]; if (self) { _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTimer:) userInfo:nil repeats:YES]; } return self; } - (void)dealloc { // 自欺欺人的写法,永远都不会执行到,除非你在外部手动invalidate这个timer [_timer invalidate]; [super dealloc]; } - (void)testTimer:(NSTimer*)timer { NSLog(@"haha!"); } @end
综上: timer都会对它的target进行retain,咱们须要当心对待这个target的生命周期问题,尤为是重复性的timer。(NSTimer初始化后,self的retainCount加1。 那么,咱们须要在释放这个类以前,执行[timer invalidate];不然,不会执行该类的dealloc方法。)
3、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.”
下面请看一个简单的例子:
- (void)applicationDidBecomeActive:(UIApplication *)application { SvTestObject *testObject2 = [[SvTestObject alloc] init]; [NSTimer scheduledTimerWithTimeInterval:1 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES]; [testObject2 release]; NSLog(@"Simulate busy"); [self performSelector:@selector(simulateBusy) withObject:nil afterDelay:3]; } // 模拟当前线程正好繁忙的状况 - (void)simulateBusy { NSLog(@"start simulate busy!"); NSUInteger caculateCount = 0x0FFFFFFF; CGFloat uselessValue = 0; for (NSUInteger i = 0; i < caculateCount; ++i) { uselessValue = i / 0.3333; } NSLog(@"finish simulate busy!"); }
例子中首先开启了一个timer,这个timer每隔1秒调用一次target的timerAction方法,紧接着咱们在3秒后调用了一个模拟线程繁忙的方法(其实就是一个大的循环)。运行程序后输出结果以下:
观察结果咱们能够发现,当线程空闲的时候timer的消息触发仍是比较准确的,可是在36分12秒开始线程一直忙着作大量运算,知道36分14秒该运算才结束,这个时候timer才触发消息,这个线程繁忙的过程超过了一个周期,可是timer并无连着触发两次消息,而只是触发了一次。等线程忙完之后后面的消息触发的时间仍然都是整数倍与开始咱们指定的时间,这也从侧面证实,timer并不会由于触发延迟而致使后面的触发时间发生延迟。
综上: timer不是一种实时的机制,会存在延迟,并且延迟的程度跟当前线程的执行状况有关。
4、NSTimer为何要添加到RunLoop中才会有做用
前面的例子中咱们使用的是一种便利方法,它实际上是作了两件事:首先建立一个timer,而后将该timer添加到当前runloop的default mode中。也就是这个便利方法给咱们形成了只要建立了timer就能够生效的错觉,咱们固然能够本身建立timer,而后手动的把它添加到指定runloop的指定mode中去。
NSTimer其实也是一种资源,若是看过多线程变成指引文档的话,咱们会发现全部的source若是要起做用,就得加到runloop中去。同理timer这种资源要想起做用,那确定也须要加到runloop中才会又效喽。若是一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。你可能会说那咱们APP的主线程的runloop咱们没有往其中添加任何资源,为何它还好好的运行。咱们不添加,不表明框架没有添加,若是有兴趣的话你能够打印一下main thread的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]; [testObject3 release]; NSLog(@"invoke release to testObject3"); } - (void)applicationWillResignActive:(UIApplication *)application { NSLog(@"SvTimerSample Will resign Avtive!"); }
这个小例子中咱们新建了一个timer,为它指定了有效的target和selector,并指出了1秒后触发该消息,运行结果以下:
观察发现这个消息永远也不会触发,缘由很简单,咱们没有将timer添加到runloop中。
综上: 必须得把timer添加到runloop中,它才会生效。
5、NSTimer加到了RunLoop中但迟迟的不触发事件
为何明明添加了,可是就是不按照预先的逻辑触发事件呢???缘由主要有如下两个:
一、runloop是否运行
每个线程都有它本身的runloop,程序的主线程会自动的使runloop生效,但对于咱们本身新建的线程,它的runloop是不会本身运行起来,当咱们须要使用它的runloop时,就得本身启动。
那么若是咱们把一个timer添加到了非主线的runloop中,它还会按照预期按时触发吗?下面请看一段测试程序:
- (void)applicationDidBecomeActive:(UIApplication *)application { [NSThread detachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:self withObject:nil]; } // 测试把timer加到不运行的runloop上的状况 - (void)testTimerSheduleToRunloop1 { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 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]]; [testObject4 release]; NSLog(@"invoke release to testObject4"); [pool release]; } - (void)applicationWillResignActive:(UIApplication *)application { NSLog(@"SvTimerSample Will resign Avtive!"); }
上面的程序中,咱们新建立了一个线程,而后建立一个timer,并把它添加当该线程的runloop当中,可是运行结果以下:
观察运行结果,咱们发现这个timer知道执行退出也没有触发咱们指定的方法,若是咱们把上面测试程序中“//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];”这一行的注释去掉,则timer将会正确的掉用咱们指定的方法。
二、mode是否正确
咱们前面本身动手添加runloop的时候,能够看到有一个参数runloopMode,这个参数是干吗的呢?
前面提到了要想timer生效,咱们就得把它添加到指定runloop的指定mode中去,一般是主线程的defalut mode。但有时咱们这样作了,却仍然发现timer仍是没有触发事件。这是为何呢?
这是由于timer添加的时候,咱们须要指定一个mode,由于同一线程的runloop在运行的时候,任意时刻只能处于一种mode。因此只能当程序处于这种mode的时候,timer才能获得触发事件的机会。
举个不恰当的例子,咱们说兄弟几个分别表明runloop的mode,timer表明他们本身的才水桶,而后一群人去排队打水,只有一个水龙头,那么同一时刻,确定只能有一我的处于接水的状态。也就是说你虽然给了老二一个桶,可是还没轮到它,那么你就得等,只有轮到他的时候你的水桶才能碰上用场。
最后一个例子我就不贴了,也很简单,须要的话,我qq发给你。
综上: 要让timer生效,必须保证该线程的runloop已启动,并且其运行的runloopmode也要匹配。
-------------------------------------------------------------------------------------------------------------
建立一个 Timer
scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
预订一个Timer,设置一个时间间隔。
表示输入一个时间间隔对象,以秒为单位,一个>0的浮点类型的值,若是该值<0,系统会默认为0.1
target:(id)aTarget
表示发送的对象,如self
selector:(SEL)aSelector
方法选择器,在时间间隔内,选择调用一个实例方法
userInfo:(id)userInfo
此参数能够为nil,当定时器失效时,由你指定的对象保留和释放该定时器。
repeats:(BOOL)yesOrNo
当YES时,定时器会不断循环直至失效或被释放,当NO时,定时器会循环发送一次就失效。
invocation:(NSInvocation *)invocation
启动 Timer
中止 Timer
Timer设置
NSTimeInterval类:是一个浮点数字,用来定义秒
PS:
NSTimer实际上是将一个监听加入的系统的RunLoop中去,当系统runloop到如何timer条件的循环时,会调用timer一次,当timer执行完,也就是回调函数执行以后,timer会再一次的将本身加入到runloop中去继续监听。
CFRunLoopTimerRef 和 NSTimer这两个类型是能够互换的, 当咱们在传参数的时候,看到CFRunLoopTimerRef能够传NSTimer的参数,增长强制转化来避免编译器的警告信息
指定(注册)一个timer到 RunLoops中, 一个timer对象只可以被注册到一个runloop中,在同一时间,在这个runloop中它可以被添加到多个runloop中模式中去。
有如下三种方法:
使用 scheduledTimerWithTimeInterval:invocation:repeats: 或者scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 这两个类方法建立一个timer并把它指定到一个默认的runloop模式中
使用 timerWithTimeInterval:invocation:repeats: 或者 timerWithTimeInterval:target:selector:userInfo:repeats:这两个类方法建立一个timer的对象,不把它知道那个到run loop. (当建立以后,你必须手动的调用NSRunLoop下对应的方法 addTimer:forMode: 去将它制定到一个runloop模式中.)
使用 initWithFireDate:interval:target:selector:userInfo:repeats: 方法分配并建立一个NSTimer的实例 (当建立以后,你必须手动的调用NSRunLoop下对应的方法 addTimer:forMode: 去将它制定到一个runloop模式中.)