iOS中最全的各种定时器使用教程
前言
相信一说到定时器,我们使用最多的就是NSTimer和GCD了,还有另外一个高级的定时器CADisplayLink;,下面将给大家详细介绍关于iOS定时器使用的相关内容,话不多说了,来一起看看详细的介绍吧。
一.NSTimer
NSTimer的初始化方法有以下几种:
会自动启动,并加入MainRunloop的NSDefaultRunLoopMode中,
注意:这里的自动启动,并不是马上就会启动,而是会延迟大概一个interval的时间:
+(NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)intervalrepeats:(BOOL)repeatsblock:(void(^)(NSTimer*timer))block
参数:
- internal:时间间隔,多久调用一次
- repeats:是否重复调用
- block:需要重复做的事情
使用:
[NSTimerscheduledTimerWithTimeInterval:1repeats:YESblock:^(NSTimer*_Nonnulltimer){ staticNSIntegernum=0; NSLog(@"%ld",(long)num); num++; if(num>4){ [timerinvalidate]; NSLog(@"end"); } }]; NSLog(@"start");
这时,控制台的输出:
2016-12-2916:29:53.901定时器[11673:278678]start 2016-12-2916:29:54.919定时器[11673:278678]0 2016-12-2916:29:55.965定时器[11673:278678]1 2016-12-2916:29:56.901定时器[11673:278678]2 2016-12-2916:29:57.974定时器[11673:278678]3 2016-12-2916:29:58.958定时器[11673:278678]4 2016-12-2916:29:58.959定时器[11673:278678]end
可以看出,这里的internal设置为1s,大概延迟了1s才开始执行block里的内容;
这里的停止定时器,我直接在block里进行的,如果使用一个全局变量来再其他地方手动停止定时器,需要这样进行:
[self.timerinvalidate]; self.timer=nil;
+(NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)tiinvocation:(NSInvocation*)invocationrepeats:(BOOL)yesOrNo
参数:
- ti:重复执行时间间隔
- invocation:NSInvocation实例,其用法见NSInvocation的基本用法
- yesOrNo:是否重复执行
示例:
//NSInvocation形式 -(void)timer2{ NSMethodSignature*method=[ViewControllerinstanceMethodSignatureForSelector:@selector(invocationTimeRun:)]; NSInvocation*invocation=[NSInvocationinvocationWithMethodSignature:method]; NSTimer*timer=[NSTimerscheduledTimerWithTimeInterval:1.0invocation:invocationrepeats:YES]; //设置方法调用者 invocation.target=self; //这里的SEL需要和NSMethodSignature中的一致 invocation.selector=@selector(invocationTimeRun:); //设置参数 ////这里的Index要从2开始,以为0跟1已经被占据了,分别是self(target),selector(_cmd) //如果有多个参数,可依次设置345... [invocationsetArgument:&timeratIndex:2]; [invocationinvoke]; NSLog(@"start"); } -(void)invocationTimeRun:(NSTimer*)timer{ staticNSIntegernum=0; NSLog(@"%ld---%@",(long)num,timer); num++; if(num>4){ [timerinvalidate]; } }
输出:
2016-12-2916:52:54.029定时器[12089:289673]0---<__NSCFTimer:0x60000017d940> 2016-12-2916:52:54.029定时器[12089:289673]start 2016-12-2916:52:55.104定时器[12089:289673]1---<__NSCFTimer:0x60000017d940> 2016-12-2916:52:56.095定时器[12089:289673]2---<__NSCFTimer:0x60000017d940> 2016-12-2916:52:57.098定时器[12089:289673]3---<__NSCFTimer:0x60000017d940> 2016-12-2916:52:58.094定时器[12089:289673]4---<__NSCFTimer:0x60000017d940>
可以看出,这里定时器是立马就执行了,没有延迟;
此方法可以传递多个参数,下面是传递两个参数的示例:
//NSInvocation形式 -(void)timer2{ NSMethodSignature*method=[ViewControllerinstanceMethodSignatureForSelector:@selector(invocationTimeRun:des:)]; NSInvocation*invocation=[NSInvocationinvocationWithMethodSignature:method]; NSTimer*timer=[NSTimerscheduledTimerWithTimeInterval:1.0invocation:invocationrepeats:YES]; //设置方法调用者 invocation.target=self; //这里的SEL需要和NSMethodSignature中的一致 invocation.selector=@selector(invocationTimeRun:des:); //设置参数 ////这里的Index要从2开始,以为0跟1已经被占据了,分别是self(target),selector(_cmd) //如果有多个参数,可依次设置345... [invocationsetArgument:&timeratIndex:2]; //设置第二个参数 NSString*dsc=@"第二个参数是字符串"; [invocationsetArgument:&dscatIndex:3]; [invocationinvoke]; NSLog(@"start"); } -(void)invocationTimeRun:(NSTimer*)timerdes:(NSString*)dsc{ staticNSIntegernum=0; NSLog(@"%ld---%@--%@",(long)num,timer,dsc); num++; if(num>4){ [timerinvalidate]; } }
输出:
2016-12-2916:57:45.087定时器[12183:292324]0---<__NSCFTimer:0x60000016dbc0>--第二个参数是字符串 2016-12-2916:57:45.088定时器[12183:292324]start 2016-12-2916:57:46.161定时器[12183:292324]1---<__NSCFTimer:0x60000016dbc0>--第二个参数是字符串 2016-12-2916:57:47.161定时器[12183:292324]2---<__NSCFTimer:0x60000016dbc0>--第二个参数是字符串 2016-12-2916:57:48.150定时器[12183:292324]3---<__NSCFTimer:0x60000016dbc0>--第二个参数是字符串 2016-12-2916:57:49.159定时器[12183:292324]4---<__NSCFTimer:0x60000016dbc0>--第二个参数是字符串
+(NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)titarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(nullableid)userInforepeats:(BOOL)yesOrNo
参数:
- ti:时间间隔
- aTarget:调用者
- aSelector:执行的方法
- userInfo:参数
- yesOrNo:是否重复执行
示例:
-(void)timer3{ NSTimer*timer=[NSTimerscheduledTimerWithTimeInterval:1target:selfselector:@selector(targetRun:)userInfo:@"这是携带的参数"repeats:YES]; NSLog(@"start"); } -(void)targetRun:(NSTimer*)timer{ staticNSIntegernum=0; NSLog(@"%ld---%@--%@",(long)num,timer,timer.userInfo); num++; if(num>4){ [timerinvalidate]; } }
输出:
2016-12-2917:05:11.590定时器[12328:296879]start 2016-12-2917:05:12.655定时器[12328:296879]0---<__NSCFTimer:0x608000162700>--这是携带的参数 2016-12-2917:05:13.661定时器[12328:296879]1---<__NSCFTimer:0x608000162700>--这是携带的参数 2016-12-2917:05:14.664定时器[12328:296879]2---<__NSCFTimer:0x608000162700>--这是携带的参数 2016-12-2917:05:15.651定时器[12328:296879]3---<__NSCFTimer:0x608000162700>--这是携带的参数 2016-12-2917:05:16.650定时器[12328:296879]4---<__NSCFTimer:0x608000162700>--这是携带的参数
下面这三种方式创建定时器的用法,和上面相应的方法类似,需要注意的是,这样创建的定时器,并不会执行,需要我们手动来开启定时器;
+(NSTimer*)timerWithTimeInterval:(NSTimeInterval)intervalrepeats:(BOOL)repeatsblock:(void(^)(NSTimer*timer))block +(NSTimer*)timerWithTimeInterval:(NSTimeInterval)titarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(nullableid)userInforepeats:(BOOL)yesOrNo +(NSTimer*)timerWithTimeInterval:(NSTimeInterval)tiinvocation:(NSInvocation*)invocationrepeats:(BOOL)yesOrNo
开启的方式是,将当前定时器添加到RunLoop中:
[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];
下面给出一个示例:
-(void)timer4{ NSTimer*timer=[NSTimertimerWithTimeInterval:1repeats:YESblock:^(NSTimer*_Nonnulltimer){ staticNSIntegernum=0; NSLog(@"%ld",(long)num); num++; if(num>4){ [timerinvalidate]; timer=nil; NSLog(@"end"); } }]; [[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode]; NSLog(@"start"); }
输出:
2016-12-2917:12:13.955定时器[12498:301751]start 2016-12-2917:12:15.013定时器[12498:301751]0 2016-12-2917:12:16.018定时器[12498:301751]1 2016-12-2917:12:17.011定时器[12498:301751]2 2016-12-2917:12:18.024定时器[12498:301751]3 2016-12-2917:12:19.023定时器[12498:301751]4 2016-12-2917:12:19.023定时器[12498:301751]end
定时器基本的创建方式就这些了,还可以设置其他的属性,例如开启时间,这些直接参考其API进行设置即可;
注意:以上实例中,我没有使用全局的NSTimer对象,如果设置全局变量,或者设置为属性,在停止定时器的时候要手动置为nil,即:
[timerinvalidate]; timer=nil;
二.GCD
dispatch_after:延迟执行一次
dispatch_after(dispatch_time_twhen,dispatch_queue_tqueue,dispatch_block_tblock)
示例:
-(void)gcdTimer{ //延迟2s dispatch_time_tdelayTime=dispatch_time(DISPATCH_TIME_NOW,2*NSEC_PER_SEC); dispatch_after(delayTime,dispatch_get_main_queue(),^(void){ NSLog(@"延迟2s后执行"); }); NSLog(@"start"); }
重复执行的定时器
void dispatch_source_set_timer(dispatch_source_tsource, dispatch_time_tstart, uint64_tinterval, uint64_tleeway)
参数:
- source:定时器
- start:开始时间,当我们使用dispatch_time或者DISPATCH_TIME_NOW时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用dispatch_walltime可以让计时器按照真实时间间隔进行计时;
- interval:间隔(如果设置为DISPATCH_TIME_FOREVER则只执行一次)
- leeway:允许的误差范围;计时不可能是百分百精确的,即使设置为0,也不是百分百精确的,所以可以设置合理的允许误差,单位:纳秒(NSEC_PER_SEC)
相关内容,可参考文章:DispatchSourceTimer的使用以及注意事项
//重复执行的定时器 -(void)gcdTimer1{ //获取全局队列 dispatch_queue_tqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); //创建定时器 dispatch_source_t_timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,queue); //开始时间 dispatch_time_tstart=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1.0*NSEC_PER_SEC)); //dispatch_time_tstart=dispatch_walltime(NULL,0); //重复间隔 uint64_tinterval=(uint64_t)(1.0*NSEC_PER_SEC); //设置定时器 dispatch_source_set_timer(_timer,start,interval,0); //设置需要执行的事件 dispatch_source_set_event_handler(_timer,^{ //在这里执行事件 staticNSIntegernum=0; NSLog(@"%ld",(long)num); num++; if(num>4){ NSLog(@"end"); //关闭定时器 dispatch_source_cancel(_timer); } }); //开启定时器 dispatch_resume(_timer); NSLog(@"start"); }
输出:
2016-12-3010:15:01.114定时器[3393:99474]start 2016-12-3010:15:02.187定时器[3393:99796]0 2016-12-3010:15:03.114定时器[3393:99796]1 2016-12-3010:15:04.186定时器[3393:99796]2 2016-12-3010:15:05.188定时器[3393:99796]3 2016-12-3010:15:06.188定时器[3393:99796]4 2016-12-3010:15:06.188定时器[3393:99796]end
这里的开始时间设置了1s的间隔,所以1s之后才开始执行,可以设置使用DISPATCH_TIME_NOW来立马执行;
注意:
这里的开始时间(start)可以使用下面的方式的来设置:
dispatch_time_tstart=dispatch_walltime(NULL,0);
或者直接设置为:DISPATCH_TIME_NOW
关于dispatch_walltime和dispatch_time的区别,上面也有提及,也可参考stackOverflow上的这个回答;主要区别就是前者在系统休眠时还会继续计时,而后者在系统休眠时就停止计时,待系统重新激活时,接着继续计时;
停止计时器:
停止GCD定时器的方式,DispatchSourceTimer的使用以及注意事项中有提及,主要有以下两种:
//关闭定时器 //完全销毁定时器,重新开启的话需要重新创建 //全局变量,关闭后需要置为nil dispatch_source_cancel(_timer); //暂停定时器 //可使用dispatch_resume(_timer)再次开启 //全局变量,暂停后不能置为nil,否则不能重新开启 dispatch_suspend(_timer);
三.CADisplayLink
CADisplayLink默认每秒运行60次,通过它的frameInterval属性改变每秒运行帧数,如设置为2,意味CADisplayLink每隔一帧运行一次,有效的逻辑每秒运行30次
屏幕刷新时调用:CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息,CADisplayLink类对应的selector就会被调用一次。所以通常情况下,按照iOS设备屏幕的刷新率60次/秒
延迟:iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会。
如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。
使用场景:从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
+(CADisplayLink*)displayLinkWithTarget:(id)targetselector:(SEL)sel
参数:
- target:调用者
- sel:执行的方法
示例:
-(void)displayLink{ CADisplayLink*display=[CADisplayLinkdisplayLinkWithTarget:selfselector:@selector(displayRun:)]; //大概1s执行一次 //取值范围1--100,值越大,频率越高 display.preferredFramesPerSecond=2; [displayaddToRunLoop:[NSRunLoopcurrentRunLoop]forMode:NSDefaultRunLoopMode]; } -(void)displayRun:(CADisplayLink*)link{ staticNSIntegernum=0; NSLog(@"%ld",(long)num); num++; if(num>4){ [linkinvalidate]; NSLog(@"end"); } }
这里的示例不太恰当,不应该在这种场合使用,
另外,我们可以使用他的paused属性,来使其暂停,或继续:
//暂停 display.paused=YES; //继续 display.paused=NO;
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。