Timer拾遗

2019-12-13  本文已影响0人  lmfei

在平时的开发中,计时器是一个常用的功能,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];
}

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

上一篇下一篇

猜你喜欢

热点阅读