iOS中的定时器

作者: helinyu | 来源:发表于2021-08-24 13:50 被阅读0次

    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

    相关文章

      网友评论

        本文标题:iOS中的定时器

        本文链接:https://www.haomeiwen.com/subject/kpediltx.html