iOS中几种定时器的介绍

背景

在iOS中我们经常会遇到一些延时任务的操作,这个时候选中一种合适的延时实现方案是很必要的,但是现在很多人只会直接的用,而不知道各种实现的区别。所以导致了遇到一些bug的时候,无从下手。接下来,我就在这边详细的介绍一下在iOS中几种定时器的区别和使用场景。

iOS中延时方案主要有以下三种:

*NSObject的方案

*NSTimer的方案

*GCD方案

用法介绍

NSObject延时实现

NSObject实现延时任务是,用到的是以下几个方法


- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

首先我们看看文档这两个方法的介绍

Invokes a method of the receiver on the current thread using the default mode after a delay.This method sets up a timer to perform theaSelectormessage on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.If you want the message to be dequeued when the run loop is in a mode other than the default mode, use theperformSelector:withObject:afterDelay:inModes: method instead. If you are not sure whether the current thread is the main thread, you can use the performSelectorOnMainThread:withObject:waitUntilDone: or performSelectorOnMainThread:withObject:waitUntilDone:modes:method to guarantee that your selector executes on the main thread. To cancel a queued message, use the cancelPreviousPerformRequestsWithTarget: or cancelPreviousPerformRequestsWithTarget:selector:object: method. (方法调用经过一段延时后,在当前线程中使用默认模式执行方法_。此方法设置的定时器任务,是在当前线程的runloop中执行的。定时器配置为运行在默认模式(nsdefaultrunloopmode)。当定时器开启,当前线程试图将消息从runloop中取出并执行。如果当前的runloop已经启动并且在默认的模式,则执行成功。如果你想该延迟操作能在多个模式下执行,那么你可以使用 _performSelector:withObject:afterDelay:inModes:方法代替。如果你不确定是否当前线程是主线程,你可以使用performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes:方法保证您的延时操作在主线程中执行。取消队列中的信息,使用cancelPreviousPerformRequestsWithTarget:cancelPreviousPerformRequestsWithTarget:selector:object:方法。

从上边的描述中我们可以看出以下关键的几点:

*该延时任务是执行在当前线程的当前的runloop中

*默认的延时任务执行模式是NSDefaultRunLoopMode

*延时任务的执行得确保当前线程的runloop是启动的。

关于最后一点,我这边验证了一下,确实如此。


- (void)viewDidLoad

{

[super viewDidLoad];

[NSThread detachNewThreadWithBlock:^{

[self performSelector:@selector(delaySome) withObject:nil afterDelay:0.00001];

//需要开启runloop,如果不开的话,delaySome方法不会被调用

@autoreleasepool {

[[NSThread currentThread] setName:@"delaySome"];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

[runLoop run];

}

}];

}

- (void)delaySome

{

NSLog(@"delaySome---%@", [NSThread currentThread]);

}

使用该方式执行任务,可以看出,任务是同步提交到当前线程的runloop中,所以当前runloop的一次运行循环中还有任务没有完成时,会先执行为执行的任务,才会执行该@selector(delaySome)任务。所以实际延迟的时间比afterDelay中指定的时间要长。

NSTimer延时实现


+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

需要将得到的NSTimer实例使用addTimer:forMode:方法添加到runloop中,才能起到作用。

//repeats: 是否重复执行

NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(delaySome) userInfo:nil repeats:NO];

[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

使用该方法,会默认添加到当前线程的runloop中去

使用NSTimer时,需要注意的时,target对象会持有timer这个实例,所以需要避免与target导致的相互持有,谁都销毁不掉的问题,最常见的当然就是ViewController中target为self,但调用-(void)invalidate却在delloc中。

显然,这种实现的模式也是添加到相应的线程中的runloop中的,也就会有上边NSObject延时实现的问题,还有一个就是销毁的问题,在网上看其他的写的关于这方面的,有些人说销毁timer实例必须在同一线程中,我这边测试了一下,显然并没有这个要求。

GCD方案

使用这种方案,我们比较常见的就是


//when:通过dispatch_time()或dispatch_walltime()返回时间。

//queue: 执行操作的队列

//block: 需要执行的操作

void dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);

//该方法是异步添加执行操作的。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

[self delaySome];

});

很显然dispatch_after实现延时任务很有优势,不用处理线程中runloop的问题,调用的对象不会被强引用。

虽然通过这种方式的有事很明显,但是他还是有一个缺点的,就是我们不能够手动把这个任务取消掉。

其实不用这种方式,我们也能够自己使用GCD实现可以取消的定时操作。


dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 1.0 * NSEC_PER_SEC);

dispatch_source_set_event_handler(timer, ^{

[self delaySome];

});

dispatch_resume(timer);

//可以通过这个当时取消定时器任务

dispatch_cancel(timer);

//如果这个过程中传递的参数中有self持有的对象,[self delaySome]中的self得改成weakSlef;比如为了方便取消任务,self持有了timer;

注意:timer没有被销毁或是取消时,会一直调用延时任务,也就是repeats为yes。

其实我们可以简单的把这个gcd的过程封装成NSTimer的一样的使用效果。

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

推荐阅读更多精彩内容

  • 一、什么是runloop 字面意思是“消息循环、运行循环”。它不是线程,但它和线程息息相关。一般来讲,一个线程一次...
    WeiHing阅读 8,142评论 11 111
  • 什么是Run Loops RunLoops是与线程相关联的基础部分,一个Run Loop就是事件处理循环,他是用来...
    傻傻小萝卜阅读 966评论 0 5
  • Runloop是iOS和OSX开发中非常基础的一个概念,从概念开始学习。 RunLoop的概念 -般说,一个线程一...
    小猫仔阅读 995评论 0 1
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    SOI阅读 21,808评论 3 63
  • 先贴下 apple doc, 本文基本是对照 doc 的翻译:https://developer.apple.co...
    brownfeng阅读 6,863评论 8 111