全部 android asp.net C/C++ cshap IOS Java javascript nodejs perl php python ruby web容器 其他 前端 数据库 第三方平台 混合式APP 网络 系统 默认分类

Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用

0 39

OC中的三种定时器:CADisplayLink、NSTimer、GCD

我们先来看看CADiskplayLink, 点进头文件里面看看, 用注释来说明下

@interface CADisplayLink : NSObject
{
@private
  void *_impl;  //指针
}

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;//唯一一个初始化方法

- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;//将创建好点实例添加到RunLoop中

- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//从RunLoop中移除
- (void)invalidate;//销毁实例

@property(readonly, nonatomic) CFTimeInterval timestamp;   //上一次Selector被调用到时间, 只读
@property(readonly, nonatomic) CFTimeInterval duration;   //屏幕刷新时间间隔, 目前iOS刷新频率是60HZ, 所以刷新时间间隔是16.7ms

@property(readonly, nonatomic) CFTimeInterval targetTimestamp CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);
//下一次被调用到时间
@property(getter=isPaused, nonatomic) BOOL paused;  //设置为YES的时候会暂停事件的触发

@property(nonatomic) NSInteger frameInterval      
  CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond");//事件触发间隔。是指两次selector触发之间间隔几次屏幕刷新,默认值为1,也就是说屏幕每刷新一次,执行一次selector,这个也可以间接用来控制动画速度
@property(nonatomic) NSInteger preferredFramesPerSecond CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0); //每秒现实多少帧@end

从头文件来看CADisplayLink的使用还是挺简单的, 下面上代码:

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    self.displayLink = [CADisplayLink displayLinkWithTarget:self
                                                   selector:@selector(logCount)];

    self.displayLink.frameInterval  = 2;    //屏幕刷新2次调用一次Selector
    
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    
}


- (void)logCount {
    
    self.count ++;
    NSLog(@"Count = %ld", self.count);
    
    if (self.count > 99) {
        
        self.count = 0;
        [self.displayLink invalidate]; //直接销毁
    }
}

代码很简单就不做说明了

需要注意的是CADisplayLink必须要添加到可以执行的RunLoop中才会执行, 当添加到某一个RunLoop后如果该RunLoop暂停或者该RunLoop的Model改变了, 计时器也会暂停

比如我们给TableView添加计时器到当前RunLoop的NSDefaultRunLoopMode model中, 当屏幕一半显示时计时器可以正常调用, 但当我们用手滑动TableView时, 计时器就会暂停。

因为当滑动时, RunLoop会进入到UITrackingRunLoopMode

所以当我们发现计时器没有运行时, 可以检查下是否有加入到正确的mode中

那我们来说一下runloop的几种mode:

  • Default模式

定义:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)

描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。

  • Connection模式

定义:NSConnectionReplyMode(Cocoa)

描述:处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。

  • Modal模式

定义:NSModalPanelRunLoopMode(Cocoa)

描述:处理modal panels事件。

  • Event tracking模式

定义:UITrackingRunLoopMode(iOS)NSEventTrackingRunLoopMode(cocoa)

描述:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。

  • Common模式

定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)

描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法向Common Modes中添加自定义modes。

注:iOS中仅NSDefaultRunLoopMode,UITrackingRunLoopMode,NSRunLoopCommonModes三种可用mode。

CADisplayLink

基本用法刚刚介绍过。

优势:依托于设备屏幕刷新频率触发事件,所以其触发时间上是最准确的。也是最适合做UI不断刷新的事件,过渡相对流畅,无卡顿感。

缺点:

  • 由于依托于屏幕刷新频率,若果CPU不堪重负而影响了屏幕刷新,那么我们的触发事件也会受到相应影响。
  • selector触发的时间间隔只能是duration的整倍数。
  • selector事件如果大于其触发间隔就会造成掉帧现象。
  • CADisplayLink不能被继承。

-------------------我是分割线---------------------

下面说说NSTImer, 一样我们直接看头文件并用注释说明

@interface NSTimer : NSObject

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;//实例化方法, 响应事件用的NSInvocation, 需要手动添加到RunLoop中才会生效
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
//实例化方法, 响应事件用的NSIvocation, 系统为自动帮你将timer添加到currentRunLoop中,defaultMode
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;//实例化方法, 响应事件用的PerformanceSelector, userInfo中可以用来传参数,需要手动添加到RunLoop中
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;//实例化方法,响应事件用的PerformanceSelector, userInfo可以用来传递参数, 系统会自动帮你将timer添加到currentRunLoop中, defaultMode

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));//实例化方法, 以block的方式传入要执行的内容, 需要手动添加到RunLoop中

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));//实例化方法, 以block的方式传入要执行的内容,系统会自动帮你将timer添加到currentRunLoop中,defaultMode

- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//跟上面类似, 只是多指定了一个开始时间
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
//跟上面类似, 只是多指定了一个开始时间
- (void)fire;  //立即执行一次定时器方法, 注意不是立即开启定时器

@property (copy) NSDate *fireDate;  //当前事件的触发事件, 一般用来做暂停和恢复
@property (readonly) NSTimeInterval timeInterval;  //只读属性, 获取当前timer的触发间隔

@property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0);  //允许的误差值

- (void)invalidate; //立即销毁timer
@property (readonly, getter=isValid) BOOL valid;  //只读属性, 获取当前timer是否有效

@property (nullable, readonly, retain) id userInfo; //只读属性, 初始化时传入的用户参数

@end

NSTimer的内容相对多一些但也更加灵活, 有一个地方需要注意的是timer开头的实例化方法需要手动添加到RunLoop, Schedule开头的会由系统帮你添加到RunLoop

  • fireDate,设置当前timer的事件的触发时间。通常我们使用这个属性来做计时器的暂停与恢复
///暂停计时器
self.timer.fireDate = [NSDate distantFuture];
///恢复计时器
self.timer.fireDate = [NSDate distantPast];
  • tolerance,允许误差时间。我们知道NSTimer事件的触发事件是不准确的,完全取决于当前runloop处理的时间。如果当前runloop在处理复杂运算,则timer执行时间将会被推迟,直到复杂运算结束后立即执行触发事件,之后再按照初始设置的节奏去执行。当设置tolerance之后在允许范围内的延迟可以触发事件,超过的则不触发。默认是时间间隔的1/10

网上很多人对fire方法的解释其实并不正确。fire并不是立即激活定时器,而是立即执行一次定时器方法

当加入到runloop中timer不需要激活即可按照设定的时间触发事件。fire只是相当于手动让timer触发一次事件

如果timer设置的repeat为NO,则fire之后timer立即销毁

如果timer的repeat为YES,则到了之前设置的时间他依旧会按部就班的触发事件

fire只是单独触发了一次事件,并不影响原timer的节奏

  • 关于invalid方法

我们知道NSTimer使用的时候如果不注意的话,是会造成内存泄漏的。原因是我们生成实例的时候,会对控制器retain一下。如果不对其进行管理则VC的永远不会引用计数为零,进而造成内存泄漏。

所以,当我们不需要的timer的时候,请如下操作:

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

这样Timer会对VC进行一次release。所以一定不要忘记调用invalid方法

顺便提一句,如果生成timer实例的时候repeat为NO,那当触发事件结束后,系统也会自动调用invalid一次

NSTimer的优势:使用相对灵活,应用广泛

劣势:受runloop影响严重,同时易造成内存泄漏(调用invalid方法解决)

-------------------我是分割线---------------------

下面说说GCD计时器:dispatch_source_t

其实dispatch_source_t说为计时器不完全正确, 它实际上是GCD给我们用的一个源对象

还是先直接上代码:

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, assign)   NSInteger count;
@property (nonatomic, strong)   dispatch_source_t tTimer;  //GCD计时器一定要设置为成员变量, 否则会立即释放

@end

@implementation ViewController

@synthesize tTimer;

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建GCD timer资源, 第一个参数为源类型, 第二个参数是资源要加入的队列
    self.tTimer = \
    dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    
    //设置timer信息, 第一个参数是我们的timer对象, 第二个是timer首次触发延迟时间, 第三个参数是触发时间间隔, 最后一个是是timer触发允许的延迟值, 建议值是十分之一
    dispatch_source_set_timer(self.tTimer,
                              dispatch_walltime(NULL, 0 * NSEC_PER_SEC),
                              0.32 * NSEC_PER_SEC,
                              0);
    
    //设置timer的触发事件
    dispatch_source_set_event_handler(self.tTimer, ^{
        
        [self logCount];
    });
    
    //激活timer对象
    dispatch_resume(self.tTimer);
}


- (void)logCount {
    
    self.count ++;
    NSLog(@"Count = %ld", self.count);
    
    if (self.count > 99) {
        
        self.count = 0;
        //暂停timer对象
        dispatch_suspend(self.tTimer);
        
        //销毁timer, 注意暂停的timer资源不能直接销毁, 需要先resume再cancel, 否则会造成内存泄漏
        //dispatch_source_cancel(self.tTimer);
    }
}

注释已经很清楚了, 就不再逐条解释

需要注意的是, GCD timer资源必须设定为成员变量, 否则会在创建完毕后立即释放

suspend挂起或暂停后的timer要先resume才能cancel, 挂起的timer直接cancel会造成内存泄漏

GCDTimer的优势:不受当前runloopMode的影响。劣势:虽然说不受runloopMode的影响,但是其计时效应仍不是百分之百准确的。

另外,他的触发事件也有可能被阻塞,当GCD内部管理的所有线程都被占用时,其触发事件将被延迟

好吧GCD我也没用玩转, 只说这些。 后面会找时间专门研究下

热忱回答0

要回复文章请先登录注册

  • 加入年费大会员(20每年)
  • 热门标签

    猜你喜欢

    1. fir.im Weekly - APP 性能监测优化 二三事
    2. iOS开发-多线程知识
    3. iOS 一个在UDP/TCP通信时,对16进制、10进制、NSData等数据之间的转换发送等处理的工具类库,一行代码将16进制字符串轻松转换成UDP需要的指令数据
    4. iOS快速集成友盟社会化分享功能(v6.1.1)
    5. iOS从零开始学习直播之音频4.歌词
    6. iOS的ATS配置
    7. iOS僵尸对象之研究
    8. iOS - 模态Model视图跳转和Push视图跳转的混合需求实现原理
    9. iOS之ProtocolBuffer搭建和示例demo
    10. js picker webapp仿ios picker

    最近热帖

    1. 注册邮箱写错了,登录进去怎么修改邮箱?
    2. curl 怎么获取地址中两个JSOM数据
    3. 2016——注定不平凡的一年 5
    4. 【Spring-web】AsyncRestTemplate源码学习 5
    5. 前端工程师所具备的技能以及顺序 7
    6. SuperSocket入门(五)-常用协议实现模版及FixedSizeReceiveFilter示例 7
    7. Git学习之路(1)-Git简介 7
    8. 创建对象与对象依赖关系 7
    9. (汉化改进作品)BruteXSS:Xss漏洞扫描脚本 9
    10. sass纯新手(一) 8

    随机文章

    1. C#夯实基础之字符串
    2. Android Studio 中Gradle配置sonarqube
    3. iOS 原生的 UIButton 点击事件是不允许带多参数的,唯一的一个参数就是默认UIButton本身 那么我们该怎么实现传递多个参数的点击事件呢?
    4. 各种进位制转换
    5. 聊聊excel生成图片的几种方式