美文网首页
Timer拾遗

Timer拾遗

作者: lmfei | 来源:发表于2019-12-13 21:04 被阅读0次

    在平时的开发中,计时器是一个常用的功能,iOS常用的计时器有三种,分别是NSTimer、CADisplayLink、GCD,下面来看一下它们的使用

    NSTimer--受Runloop影响,相对不准确

    NSTimer的创建方式有三种:

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

    此种方式需要将timer加到runloop才会执行

    //方式二
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    

    创建后timer直接执行

    //方式三
    + (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));
    

    创建后timer直接执行,iOS 10后API

    方式一的具体使用

    在viewDidLoad中执行

    - (void) timer_start_test {
            if (self.timer == nil) {
                self.timer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
                [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
           }
    }
    

    在didMoveToParentViewController中执行

    - (void)timer_stop_test {
        //手动销毁计时器
        if (self.timer) {
            [self.timer invalidate];
            self.timer = nil;
        }
    }
    
    方式二的具体使用

    在viewDidLoad中执行

    - (void) timer_start_test {
            if (self.timer == nil) {
                 self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];   
            }
    }
    

    关闭方式与第一种相同

    方式三的具体使用
    - (void) timer_start_test {
            if (self.timer == nil) {
                self.timer = [NSTimer scheduledTimerWithTimeInterval:1. repeats:YES block:^(NSTimer * _Nonnull timer) {
                    NSLog(@"lmf block:%@", [NSDate date]);
                }];
            }
    }
    

    关闭方式在dealloc中执行timer_stop_test方法

    注:前两种创建方式需要手动销毁计时器,否则会引起循环引用,第三种创建方式需要在dealloc中停止计时器,否则计时器不会停止,具体实现代码见后文

    CADisplayLink--与屏幕刷新频率同步的定时器

    使用方式:

    在viewDidLoad中开启,didMoveToParentViewController中关闭

    - (void) displayLink_start_test {
        if (self.displayLink_timer == nil) {
            self.displayLink_timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerAction)];
            //iOS 10之前frameInterval 之后preferredFramesPerSecond,设置N帧执行一次
            self.displayLink_timer.preferredFramesPerSecond  = 10;
            
            //设置
            [self.displayLink_timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        }
    }
    
    - (void) displayLink_stop_test {
        [self.displayLink_timer invalidate];
        self.displayLink_timer = nil;
    }
    

    GCD--不受Runloop影响,精确度最小可以精确到纳秒级

    在viewDidLoad中开启,didMoveToParentViewController中关闭

    - (void) gcd_start_test {
        //GCD 创建的Timer,不受Runloop影响,精确度最小可以精确到纳秒级
        if (self.gcd_timer == nil) {
            dispatch_queue_t queue = dispatch_get_main_queue();
            dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
            dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
            dispatch_source_set_event_handler(timer, ^{
                [self timerAction];
            });
            //启动/唤醒
            dispatch_resume(timer);
            
            //需要强引用计时器,否则执行到},会被释放
            self.gcd_timer = timer;
        }
    }
    
    - (void) gcd_stop_test {
        //挂起计时器
        //    dispatch_suspend(self.gcd_timer);
        
        //取消计时器
        dispatch_cancel(self.gcd_timer);
        self.gcd_timer = nil;
    }
    

    NSTimer循环引用的解决方案

    在合适的时机,关闭计时器

    上面介绍计时器使用时,在didMoveToParentViewController里关闭计时器就是通过这种方案

    通过中间者接受代理,这种方案需要在dealloc里停止计时器,否则self被销毁时,target找不到方法会导致崩溃
    - (void)targetMove {
        self.target = [NSObject new];
        class_addMethod([self.target class], @selector(timerAction), class_getMethodImplementation([self class], @selector(timerAction)), "v@:");
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self.target selector:@selector(timerAction) userInfo:nil repeats:YES];
    }
    
    通过proxy

    首先自定义一个proxy类

    @interface LProxy : NSProxy
    
    @property (nonatomic, weak) id target;
    
    @end
    
    @implementation LProxy
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return [self.target methodSignatureForSelector:sel];
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        [invocation invokeWithTarget:self.target];
    }
    
    @end
    

    然后将代理对象交给proxy,需要在对象销毁时,取消计时器

    - (void)proxy_test {
        _lproxy = [LProxy alloc];
        _lproxy.target = self;
        _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_lproxy selector:@selector(timerAction) userInfo:nil repeats:YES];
    }
    
    通过实现扩展Block实现

    实现代码

    #import <Foundation/Foundation.h>
    
    typedef void (^ExecuteTimerBlock)(NSTimer *timer);
    
    @interface NSTimer (Block)
    
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti repeats:(BOOL)yesOrNo executeBlock:(ExecuteTimerBlock) block;
    
    @end
    
    
    #import "NSTimer+Block.h"
    
    @implementation NSTimer (Block)
    
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti repeats:(BOOL)yesOrNo executeBlock:(ExecuteTimerBlock) block {
        NSTimer *timer = [self scheduledTimerWithTimeInterval:ti target:self selector:@selector(timerAction:) userInfo:[block copy] repeats:yesOrNo];
        return timer;
    }
    
    + (void)timerAction:(NSTimer *)timer {
        ExecuteTimerBlock blcok = timer.userInfo;
        if (blcok) {
            blcok(timer);
        }
    }
    

    调用

    NSTimer *timer;
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"自定义Timer Block");
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    - (void)dealloc {
        NSLog(@"%s", __FUNCTION__);
        [timer invalidate];
    }
    

    生活如此美好,今天就点到为止。。。

    相关文章

      网友评论

          本文标题:Timer拾遗

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