定时器NSTimer的使用

原文链接

你可以使用的定时器类来创建一个定时器对象,或者更简单地说,定时器。一个定时器在指定的时间到达后触发,并发送指定的消息到目标对象。例如你可以通过定时器来每间隔一段时间通知窗口刷新显示。

定时器常和运行循环一起使用。要有效的使用定时器,你需要注意运行循环的相关知识,可以参考NSRunLoop和Threading Programming指南。特别要注意的是,运行循环对拥有的定时器进行强引用,所以如果定时器已经加入了运行循环,那么你自己不需要再强引用这个定时器。

定时器并不是实时机制;只有在定时器所在的运行循环的那个模式正在运行且能够检测时间间隔是否已经达到的情况下才会被触发。因为一个典型的运行循环管理各种输入源,所以定时器的时间间隔的有效分辨率被限制为50-100毫秒的数量级。如果定时器的触发时间恰好处于运行循环外调期间,或者恰好处于运行循环不监控定时器的模式下,则定时器直到下个时间间隔才会被检查触发。另请参考Timer Tolerance(定时器公差)。

定时器是一个“toll-free bridged”,其Core Foundation副本CFRunLoopTimerRef。请参考Toll-Free Bridging来获得更多关于toll-free bridging的信息。

重复VS非重复计时器

你可以在创建的时候指定定时器是否重复。非重复的计时器触发一次后自动自动失效,防止计时器再次被触发。相比之下,重复定时器触发后会重新加入到相同的运行循环中。
重复计时器总是基于预定时间而不是实际时间来安排下次触发。例如一个定时器设定每5秒钟触发一次,预定触发时间将总是在原触发时间后的5s,即使实际的触发时间被延迟。如果触发时间延迟了一个或多个时间间隔,则在这段时间内只触发一次;触发后重新安排下次触发时间。

时间公差

在iOS7和更高版本以及OS X v10.9和更高版本,您可以指定计时器的公差。允许在使用定时器增强系统的能力来优化节能和增加响应性方面的灵活性。可能在设定时间和设定时间加上公差时间之内的任何时间触发。计时器不会在预定的触发时间前触发。对于重复定时器,下一个触发时间总是基于原始触发时间来计算的而不是计入公差触发时间,这样可以防止偏移。 默认值是0即无公差 。对于某些计时器,在用户设置的公差范围外,系统保留适量的公差。

作为定时器的使用者,你自己最清楚一个定时器可以允许的公差应该设置为多少。根据一般经验规则,对于重复定时器,公差一般设置为间隔的至少10%. 即使少量的公差也会对系统电量使用有显著积极的影响。系统可能使用公差的最大值。

在运行循环中调度计时器

一个定时器对象同时只能被加入到一个运行循环中,虽然它在那个运行循环内可以被添加到多个运行循环模式中。有三种方法来创建一个定时器:

  • 通过 scheduledTimerWithTimeInterval:invocation:repeats:或者scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class方法来创建定时器并将定时器加入到当前运行循环的默认模式中。

  • 通过timerWithTimeInterval:invocation:repeats:或者timerWithTimeInterval:target:selector:userInfo:repeats: class方法创建一个定时器对象,不加入任何运行循环。(创建完之后,你必须通过调用 NSRunLoop 对象的addTimer:forMode:方法手动将定时器加入一个运行循环)

  • 通过initWithFireDate:interval:target:selector:userInfo:repeats:方法创建并初始化一个定时器. (创建完之后,你必须通过调用 NSRunLoop 对象的addTimer:forMode:方法手动将定时器加入一个运行循环)

一旦定时器加入到了运行循环中,定时器将在指定的时间触发,直到定时器失效。一个非重复的定时器在触发后将会自动失效。而对于重复的定时器,你必须通过调用定时器对象的取消方法来将其取消。
调用这个方法需要讲定时器从当前的运行循环中移除; 所以, 你应当在创建定时器的线程中调用这个取消的方法。 取消的方法将使定时器立即失效以不再影响运行循环。运行循环接着移除定时器(同时移除对定时器的强引用) ,无论是在取消的方法刚刚返回或者是返回后的某个时刻。一旦取消,定时器对象不能再被使用。

一个重复的定时器被触发后,会讲下一次触发安排在一个整数乘以时间间隔后最靠近这次触发时间的时间点,并在允许的公差范围内。

如果执行selector或者函数调用的时间比设定的时间间隔长,定时器将只在下个时间间隔触发;也就是说,计时器不会弥补执行selector或者函数调用期间所丢失的触发。

子类注意事项

你不应该试图继承定时器类.

创建一个定时器

  • scheduledTimerWithTimeInterval:invocation:repeats:
  • scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
  • timerWithTimeInterval:invocation:repeats:
  • timerWithTimeInterval:target:selector:userInfo:repeats:
  • initWithFireDate:interval:target:selector:userInfo:repeats:
    Designated Initializer

触发定时器

  • fire

关闭定时器

  • invalidate

定时器相关函数

valid
fireDate
timeInterval
userInfo
Firing Tolerance

实际使用中注意事项

创建一个定时器:

NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 2.0
                      target: self
                      selector:@selector(onTick:)
                      userInfo: nil repeats:NO];
  • 如果repeats设为NO, 则定时器会等待2秒,然后调用selector ,之后再关闭定时器;
  • 如果repeats设为YES, 则定时器会立即调用selector,之后每隔2秒调用一次selector;要关闭定时器需要调用 [t invalidate];
  • 以上方法创建的定时器,默认是加入到主线程的NSDefaultRunLoopMode模式中。当主线程处理某些UI事件时,比如UIScrollView的拖动操作,主线程将会切换成NSEventTrackingRunLoopMode模式,这个模式不会处理NSDefaultRunLoopMode模式中注册的事件。也就是说此时这个Timer就不会执行,直到主线程恢复到NSDefaultRunLoopMode模式。若想要主线程处理UI时不影响Timer的执行,可讲Timer加入到NSRunLoopCommonModes模式。这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合。另一种方法,将Timer加入到非主线程的运行循环中,则主线程中的操作影响不到Timer的执行。若Timer中需要UI操作,可切换回主线程进行UI操作。
  • 另外一个选择,你可以通过这样简单的语句来替代非重复的定时器:
[self performSelector:@selector(onTick:) withObject:nil afterDelay:2.0];

指定时间间隔后的重复定时器

NSDate *d = [NSDate dateWithTimeIntervalSinceNow: 60.0];
NSTimer *t = [[NSTimer alloc] initWithFireDate: d
                              interval: 1
                              target: self
                              selector:@selector(onTick:)
                              userInfo:nil repeats:YES];

NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer:t forMode: NSDefaultRunLoopMode];
[t release];

以上定时器在60秒后第一次触发,之后每隔1秒触发一次。

任意时间启动重复定时器

NSMethodSignature *sgn = [self methodSignatureForSelector:@selector(onTick:)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature: sgn];
[inv setTarget: self];
[inv setSelector:@selector(onTick:)];

NSTimer *t = [NSTimer timerWithTimeInterval: 1.0
                      invocation:inv 
                      repeats:YES];

这之后你可以通过如下方法在任意时间启动定时器:

NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer: t forMode: NSDefaultRunLoopMode];

onTick函数可以这样写:

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

推荐阅读更多精彩内容