[iOS]各种定时器–最全的定时器使用

[iOS]各种定时器–最全的定时器使用

大家好,又见面了,我是全栈君。

说到定时器, 我们使用最多的就是NSTimerGCD 了, 还有另外一个高级的定时器 CADisplayLink;

一. NSTimer

NSTimer的初始化方法有以下几种:
会自动启动, 并加入 MainRunloop NSDefaultRunLoopMode 中,

注意: 这里的自动启动, 并不是马上就会启动, 而是会延迟大概一个interval的时间:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block

参数:

  • internal : 时间间隔, 多久调用一次

  • repeats: 是否重复调用

  • block: 需要重复做的事情

使用:

 [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
        static NSInteger num = 0;
        
        NSLog(@"%ld", (long)num);
        num++;
        
        if (num > 4) {
            
            [timer invalidate];
            
            NSLog(@"end");
        }
    }];
    
    NSLog(@"start");

这时, 控制台的输出:

2016-12-29 16:29:53.901 定时器[11673:278678] start
2016-12-29 16:29:54.919 定时器[11673:278678] 0
2016-12-29 16:29:55.965 定时器[11673:278678] 1
2016-12-29 16:29:56.901 定时器[11673:278678] 2
2016-12-29 16:29:57.974 定时器[11673:278678] 3
2016-12-29 16:29:58.958 定时器[11673:278678] 4
2016-12-29 16:29:58.959 定时器[11673:278678] end

可以看出, 这里的internal设置为1s, 大概延迟了1s才开始执行block里的内容;

这里的停止定时器, 我直接在block里进行的, 如果使用一个全局变量来再其他地方手动停止定时器,需要这样进行:

[self.timer invalidate];
self.timer = nil;

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

参数:

示例:

// NSInvocation形式
- (void)timer2 {
    
    NSMethodSignature *method = [ViewController instanceMethodSignatureForSelector:@selector(invocationTimeRun:)];
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:method];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 invocation:invocation repeats:YES];
    
    // 设置方法调用者
    invocation.target = self;
    
    // 这里的SEL需要和NSMethodSignature中的一致
    invocation.selector = @selector(invocationTimeRun:);
    
    // 设置参数
    // //这里的Index要从2开始,以为0跟1已经被占据了,分别是self(target),selector(_cmd)
    // 如果有多个参数, 可依次设置3 4 5 ...
    [invocation setArgument:&timer atIndex:2];
    
    [invocation invoke];
    
    NSLog(@"start");
}
- (void)invocationTimeRun:(NSTimer *)timer {
    
    static NSInteger num = 0;
    NSLog(@"%ld---%@", (long)num, timer);
    
    num++;
    
    if (num > 4) {
        [timer invalidate];
    }
}

输出:

2016-12-29 16:52:54.029 定时器[12089:289673] 0---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:54.029 定时器[12089:289673] start
2016-12-29 16:52:55.104 定时器[12089:289673] 1---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:56.095 定时器[12089:289673] 2---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:57.098 定时器[12089:289673] 3---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:58.094 定时器[12089:289673] 4---<__NSCFTimer: 0x60000017d940>

可以看出, 这里定时器是立马就执行了, 没有延迟;
此方法可以传递多个参数, 下面是传递两个参数的示例:

// NSInvocation形式
- (void)timer2 {
    
    NSMethodSignature *method = [ViewController instanceMethodSignatureForSelector:@selector(invocationTimeRun:des:)];
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:method];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 invocation:invocation repeats:YES];
    
    // 设置方法调用者
    invocation.target = self;
    
    // 这里的SEL需要和NSMethodSignature中的一致
    invocation.selector = @selector(invocationTimeRun:des:);
    
    // 设置参数
    // //这里的Index要从2开始,以为0跟1已经被占据了,分别是self(target),selector(_cmd)
    // 如果有多个参数, 可依次设置3 4 5 ...
    [invocation setArgument:&timer atIndex:2];
    // 设置第二个参数
    NSString *dsc = @"第二个参数是字符串";
    [invocation setArgument:&dsc atIndex:3];
    
    [invocation invoke];
    
    NSLog(@"start");
}
- (void)invocationTimeRun:(NSTimer *)timer des:(NSString *)dsc {
    
    static NSInteger num = 0;
    NSLog(@"%ld---%@--%@", (long)num, timer, dsc);
    
    num++;
    
    if (num > 4) {
        [timer invalidate];
    }
}

输出:

2016-12-29 16:57:45.087 定时器[12183:292324] 0---<__NSCFTimer: 0x60000016dbc0>--第二个参数是字符串
2016-12-29 16:57:45.088 定时器[12183:292324] start
2016-12-29 16:57:46.161 定时器[12183:292324] 1---<__NSCFTimer: 0x60000016dbc0>--第二个参数是字符串
2016-12-29 16:57:47.161 定时器[12183:292324] 2---<__NSCFTimer: 0x60000016dbc0>--第二个参数是字符串
2016-12-29 16:57:48.150 定时器[12183:292324] 3---<__NSCFTimer: 0x60000016dbc0>--第二个参数是字符串
2016-12-29 16:57:49.159 定时器[12183:292324] 4---<__NSCFTimer: 0x60000016dbc0>--第二个参数是字符串
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo

参数:

  • ti: 时间间隔

  • aTarget: 调用者

  • aSelector: 执行的方法

  • userInfo: 参数

  • yesOrNo: 是否重复执行

示例:

- (void)timer3 {
    
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(targetRun:) userInfo:@"这是携带的参数" repeats:YES];
    
    NSLog(@"start");
}
- (void)targetRun:(NSTimer *)timer {
    
    static NSInteger num = 0;
    
    NSLog(@"%ld---%@--%@", (long)num, timer, timer.userInfo);
    
    num++;
    
    if (num > 4) {
        [timer invalidate];
    }
}

输出:

2016-12-29 17:05:11.590 定时器[12328:296879] start
2016-12-29 17:05:12.655 定时器[12328:296879] 0---<__NSCFTimer: 0x608000162700>--这是携带的参数
2016-12-29 17:05:13.661 定时器[12328:296879] 1---<__NSCFTimer: 0x608000162700>--这是携带的参数
2016-12-29 17:05:14.664 定时器[12328:296879] 2---<__NSCFTimer: 0x608000162700>--这是携带的参数
2016-12-29 17:05:15.651 定时器[12328:296879] 3---<__NSCFTimer: 0x608000162700>--这是携带的参数
2016-12-29 17:05:16.650 定时器[12328:296879] 4---<__NSCFTimer: 0x608000162700>--这是携带的参数

下面这三种方式创建定时器的用法, 和上面相应的方法类似, 需要注意的是, 这样创建的定时器, 并不会执行, 需要我们手动来开启定时器;

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo

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

开启的方式是, 将当前定时器添加到RunLoop中:

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

下面给出一个示例:

- (void)timer4 {
    
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
        static NSInteger num = 0;
        
        NSLog(@"%ld", (long)num);
        num++;
        
        if (num > 4) {
            
            [timer invalidate];
            timer = nil;
            
            NSLog(@"end");
        }
    }];
    
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    NSLog(@"start");
}

输出:

2016-12-29 17:12:13.955 定时器[12498:301751] start
2016-12-29 17:12:15.013 定时器[12498:301751] 0
2016-12-29 17:12:16.018 定时器[12498:301751] 1
2016-12-29 17:12:17.011 定时器[12498:301751] 2
2016-12-29 17:12:18.024 定时器[12498:301751] 3
2016-12-29 17:12:19.023 定时器[12498:301751] 4
2016-12-29 17:12:19.023 定时器[12498:301751] end

定时器基本的创建方式就这些了, 还可以设置其他的属性, 例如开启时间, 这些直接参考其API 进行设置即可;

注意: 以上实例中, 我没有使用全局的NSTimer 对象, 如果设置全局变量, 或者设置为属性, 在停止定时器的时候要手动置为nil, 即:

[timer invalidate];
 timer = nil;

二. GCD

dispatch_after : 延迟执行一次
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)

示例:

- (void)gcdTimer {
    
    // 延迟2s
    dispatch_time_t delayTime = 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_t source,
    dispatch_time_t start,
    uint64_t interval,
    uint64_t leeway)

参数:

  • source: 定时器

  • start: 开始时间, 当我们使用 dispatch_time 或者 DISPATCH_TIME_NOW 时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用 dispatch_walltime 可以让计时器按照真实时间间隔进行计时;

  • interval: 间隔(如果设置为 DISPATCH_TIME_FOREVER 则只执行一次)

  • leeway: 允许的误差范围; 计时不可能是百分百精确的, 即使设置为0, 也不是百分百精确的, 所以可以设置合理的允许误差, 单位: 纳秒(NSEC_PER_SEC)


相关内容, 可参考文章: Dispatch Source Timer 的使用以及注意事项

// 重复执行的定时器
- (void)gcdTimer1 {
    
    // 获取全局队列
    dispatch_queue_t queue = 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_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    
//    dispatch_time_t start = dispatch_walltime(NULL, 0);
    
    // 重复间隔
    uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
    
    // 设置定时器
    dispatch_source_set_timer(_timer, start, interval, 0);
    
    // 设置需要执行的事件
    dispatch_source_set_event_handler(_timer, ^{
        
        //在这里执行事件
        static NSInteger num = 0;
        
        NSLog(@"%ld", (long)num);
        num++;
        
        if (num > 4) {
            
            NSLog(@"end");
            
            // 关闭定时器
            dispatch_source_cancel(_timer);
        }
    });
    // 开启定时器
    dispatch_resume(_timer);
    
    NSLog(@"start");
}

输出:

2016-12-30 10:15:01.114 定时器[3393:99474] start
2016-12-30 10:15:02.187 定时器[3393:99796] 0
2016-12-30 10:15:03.114 定时器[3393:99796] 1
2016-12-30 10:15:04.186 定时器[3393:99796] 2
2016-12-30 10:15:05.188 定时器[3393:99796] 3
2016-12-30 10:15:06.188 定时器[3393:99796] 4
2016-12-30 10:15:06.188 定时器[3393:99796] end

这里的开始时间设置了1s的间隔, 所以1s之后才开始执行,可以设置使用DISPATCH_TIME_NOW来立马执行;

注意:
这里的开始时间(start)可以使用下面的方式的来设置:

dispatch_time_t start = dispatch_walltime(NULL, 0);

或者直接设置为: DISPATCH_TIME_NOW


关于 dispatch_walltime dispatch_time 的区别, 上面也有提及,也可参考stackOverflow上的这个回答; 主要区别就是前者在系统休眠时还会继续计时, 而后者在系统休眠时就停止计时, 待系统重新激活时, 接着继续计时;

停止计时器:
停止GCD定时器的方式, Dispatch Source Timer 的使用以及注意事项中有提及, 主要有以下两种:

// 关闭定时器
// 完全销毁定时器, 重新开启的话需要重新创建
// 全局变量, 关闭后需要置为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)target selector:(SEL)sel

参数:

  • target: 调用者

  • sel: 执行的方法

示例:

- (void) displayLink {
    
    CADisplayLink *display = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayRun:)];
    
    // 大概1s执行一次
// 取值范围 1--100, 值越大, 频率越高
    display.preferredFramesPerSecond = 2;
    
    [display addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)displayRun:(CADisplayLink *)link {
    
    static NSInteger num = 0;
    
    NSLog(@"%ld", (long)num);
    num++;
    
    if (num > 4) {
        
        [link invalidate];
        
        NSLog(@"end");
    }
}

这里的示例不太恰当, 不应该在这种场合使用,
另外, 我们可以使用他的 paused 属性, 来使其暂停, 或继续:

// 暂停
    display.paused = YES;
// 继续
    display.paused = NO;
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/108414.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • .net 4.0 ValidateRequest=”false” 无效「建议收藏」

    .net 4.0 ValidateRequest=”false” 无效「建议收藏」
    当你在安装了.NETFramework4.0以上版本后,当你的应用程序以.NETFramework4.0为框架版本,你的任意服务器请求,都将被进行服务器请求验证(ValidationRequest),这不仅包括ASP.NET,同时也包括WebServices等各种HTTP请求,不仅仅针对aspx页面,也针对HTTPHandler,HTTPModule等,因为这个验证(Valify)的过程,将会发生在BeginRequest事件之前。
       问题的解决方案就是在全局级别(

    2022年6月6日
    38
  • javase学习笔记

    javase学习笔记01.01_计算机基础知识(计算机概述)(了解)A:什么是计算机?计算机在生活中的应用举例计算机(Computer)全称:电子计算机,俗称电脑。是一种能够按照程序运行,自动、高速处理海量数据的现代化智能电子设备。由硬件和软件所组成,没有安装任何软件的计算机称为裸机。常见的形式有台式计算机、笔记本计算机、大型计算机等。应用举例1:科学计算2、数据处理3、自动控制4、计算机辅助设计…

    2022年5月13日
    32
  • 小波变换原理_小波变换的缺点

    小波变换原理_小波变换的缺点https://www.cnblogs.com/warmbeast/p/7809286.html从傅里叶变换到小波变换,并不是一个完全抽象的东西,可以讲得很形象。小波变换有着明确的物理意义,如果我们从它的提出时所面对的问题看起,可以整理出非常清晰的思路。下面就按照傅里叶–>短时傅里叶变换–>小波变换的顺序,讲一下为什么会出现小波这个东西、小波究竟是怎样的思路。傅里叶变换关于傅…

    2025年6月21日
    2
  • Chrome performance_开发者模式玩游戏流畅

    Chrome performance_开发者模式玩游戏流畅主要有4方面:(对照下图)1.控制按钮。2.overview。页面性能的高级汇总(FPS:帧率,CPU:CPU占用,NET:网络请求)3.火焰图。CPU堆叠追踪的可视化(左侧名称是主线程的各种事件,Network:网络请求详细情况)4.数据统计。以图表的形式汇总数据(summary:统计报表,Bottom-Up:事件时长顺序,CallTree:事件调用顺序,Eventlog:事件发生的……

    2025年7月9日
    4
  • win10eplan激活码破解步骤3月最新在线激活

    win10eplan激活码破解步骤3月最新在线激活,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月15日
    125
  • 各种数据库的分页查询语句[通俗易懂]

    各种数据库的分页查询语句

    2022年3月2日
    46

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号