人生几何?

iOS中的定时器

2021-08-24  本文已影响0人  helinyu

oX01 NSTimer

常规的定时器,不需要精确的定时器间隔的操作,这个和runloop有关系

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (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));
+ (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));
  1. time 开头的是没有启动循环调用计划【需要调用fire】, 而scheduled这个是启动了调用计划。
    因为timer持有这个对象, 所以,很容易造成循环引用。
    一个是声明weak来进行破坏循环引用, 一个是使用block来间接破坏引用。
1、使用block来解除循环引用

这个方式为何能够解决循环引用问题? 这段代码将计时器所应执行的任务封装成“块”, 在迪奥哟共计时器函数时,, 把它所谓userInfo参数传进去。 该参数可用来存放“不透明值”, 只要计时器还有效, 就会一直保留它。 传入参数时候, 需要copy到堆上面保活。计时器现在的target 是NSTimer类对象, 这是一个单例, 因此计时器是否会保留它, 其实都无所谓。 此处依然有循环引用。 因为类对象无需回收,所以不用担心。

使用我们block方式引用NSTimer

这里使用,先定义一个弱引用,再转化为强引用保持。 如果这个类对象释放,回收过程还会调用计时器的invalidate方法,这样的话,计时器就不会再执行任务了。此处使用weak应用还能零程序更加安全,因为这时开发者可能在编写dealloc时候忘了调用计时器的invalidate方法,从而导致计时器再次运行。 若发生此类情况,则块里的weakself 会变成nil。

NOTE: block 里面如果没有使用weak来修饰的话,这个是有问题的,还是有循环引用的,这个VC并不会调用dealloc方法,
eg: [self doStomthing] ;

这样的循环图:


[self doSomething 的时候造成的闭环, 所以,self要改成weak调用]

直接使用weak声明Timer也会造成持有的问题
@property (nonatomic, weak) NSTimer *timer; 即使有这样的声明,也是不行的,不会调用dealloc,因为timer还在执行,time强持有self,所有self不会被释放掉【这个时候self的引用计数不是0】。 这个时候timer一直在执行这个内容。

2、中间使用一个对象weak持有Timer, (和上面的block类似)
#import "HWWeakTimer.h"

@interface HWWeakTimerTarget : NSObject

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;

@end

@implementation HWWeakTimerTarget

- (void) fire:(NSTimer *)timer {
    if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
    } else {
        [self.timer invalidate];
    }
}

@end

@implementation HWWeakTimer

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats {
    HWWeakTimerTarget* timerTarget = [[HWWeakTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                         target:timerTarget
                                                       selector:@selector(fire:)
                                                       userInfo:userInfo
                                                        repeats:repeats];
    return timerTarget.timer;
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(HWTimerHandler)block
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats {
    NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
    if (userInfo != nil) {
        [userInfoArray addObject:userInfo];
    }
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(_timerBlockInvoke:)
                                       userInfo:[userInfoArray copy]
                                        repeats:repeats];
}

+ (void)_timerBlockInvoke:(NSArray*)userInfo {
    HWTimerHandler block = userInfo[0];
    id info = nil;
    if (userInfo.count == 2) {
        info = userInfo[1];
    }
    // or `!block ?: block();` @sunnyxx
    if (block) {
        block(info);
    }
}
@end
第一个target方法的破解循环引用问题

第二个方法和yy库里面的block方式是一样的。 也会要注意block里面要用week修饰过的对象

3、 第2种的变种创建一个代理对象 :YYWeakProxy

使用实例 :
    self.timer= [NSTimer scheduledTimerWithTimeInterval:0.1 target:[YYWeakProxy proxyWithTarget:self] selector:@selector(onScheduled:) userInfo:nil repeats:YES];

NOTE: [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 注意timer一般要加入commmonModes


oX02 CADisplayLink

CADisplayLink 要运行,需要手动添加runloop的模式, 如果不添加默认是pause,不会运行

//创建
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel; 

// 添加runloop
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;

// 移除runloop
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;

- (void)invalidate; // 失效

@property(readonly, nonatomic) CFTimeInterval timestamp;
//  这次刷新的时间点【其值是基于一个随机值开始的递增值】
//可以两次时间相减之后得到时间间隔

@property(readonly, nonatomic) CFTimeInterval duration;
// 屏幕刷新一次的时候的时间间隔, 我们iOS1s刷新60次,所以这里的时间是: 1/60s

>selector的调用间隔时间计算方式是:间隔时间=duration×frameInterval。

@property(readonly, nonatomic) CFTimeInterval targetTimestamp
    API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0));

// 暂停
@property(getter=isPaused, nonatomic) BOOL paused;

// 帧间隔时间
// 默认是1 ,表示1s刷新60次, 如果设置为2,表示1s内刷新30次 , 这个60次是和UI的刷新频率是一样的
@property(nonatomic) NSInteger frameInterval
  API_DEPRECATED("preferredFramesPerSecond", ios(3.1, 10.0), 
                 watchos(2.0, 3.0), tvos(9.0, 10.0));

@property(nonatomic) NSInteger preferredFramesPerSecond
    API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0));

一般用于做UI界面的不停重绘,比如自定义动画引擎或者视频播放的渲染。

NSTimer 和CADisplayLink区别
CADisplayLink

使用示例

使用上和NSTimer没有太大差别,是要是添加runloop这里小小差别。 还有就是这个主要用在和UI刷新相关的。eg: UI上面的动画绘制等等。


oX03 【精确定时器】

dispatch_source_t的定时器不受RunLoop影响,而且dispatch_source_t是系统级别的源事件,精度很高,系统自动触发。

首先明确:dispatch_source_t源事件有一种类型就是DISPATCH_SOURCE_TYPE_TIMER——用来计时的

// 示例代码
//(1)创建源事件
//(2)设置定时器事件
//(3)设置事件触发的回调
//(4)运行
- (void)testGcdSource{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
    dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(_timer, ^{
  // 处理的内容
    });
    dispatch_resume(_timer);
}

TGCDTimer
YYTimer


NSTimer、 CADisplayLink、以及GCD里面的source的原理和区别。

NSTimer都是和Runloop有很大的关系的, runloop是运行时的系统 ;runloop
dispatch_source和runloop是没有关系的,系统触发的

使用方面:
和UI有关的的使用CADisplayLink , 如果要去精确使用dispatch_source, 其他的使用NSTimer 。

NSTimer 和CFRunLoopTimerRef (定时源)

CFRunLoopTimerRef 和NSTimer是Tool-Free Bridged 关系。 它包含一个时间长度和一个回调(函数指针)。 当其加入到runloop时,runloop会注册对应的时间点,当时间到时,Runloop会被唤醒以执行那个回调。

NSTimer 默认Runloop是default

上一篇下一篇

猜你喜欢

热点阅读