美文网首页iOS UI开发
iOS实战-更精准的定时器

iOS实战-更精准的定时器

作者: GemShi | 来源:发表于2019-02-08 17:02 被阅读9次

    iOS中,常用的定时器有三种:NSTimer,CADisplayLink,GCD。在一定基础之上,做进一步探究。

    NSTimer,CADisplayLink

    在使用scheduleTimerWithTimeInterval:target:selector:userInfo:repeats:方式创建的定时器会以默认方式添加到当前线程runloop中,无需手动添加。
    如果有需求:点击屏幕触发定时器,不需要时点击返回

    @interface TestViewController ()
    
    @property(nonatomic,strong)NSTimer *timer;
    
    @end
    
    @implementation TestViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    }
    
    -(void)run
    {
        NSLog(@"%s",__func__);
    }
    
    -(void)dealloc
    {
        [self.timer invalidate];
        self.timer = nil;
        
        NSLog(@"%s",__func__);
    }
    
    @end
    

    经测试,这段代码dealloc方法不会执行,存在循环引用。因为NSTimer内部有强指针target,所以不管外部传入的是weak还是strong,都会将传入的内存地址赋值给形参target,所以不管target存储的是weak的地址还是strong的地址,NSTimer对target都是强引用,所以timer和self产生了循环引用。

    通过GNUStep中源码可以看出,传入的object被retain一次,被timer强持有。

    NSTimer schedule:target: init

    代码所产生的循环引用

    timer-self循环引用

    要想解决这个循环引用,使其中一个强引用变成弱引用。


    打破循环引用
    • 方法一:使用block创建方式
      block存储在timer中,timer对block强引用,block对self是弱引用,定时器对self是弱引用。
    __weak typeof(self)wself = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
      [wself run];
    }];
    
    • 方法二
      如果使用scheduleTimerWithTimeInterval:target:方法,就要引入中间变量,将中间对象的指针置为弱指针。
    @interface SSProxy : NSObject
    //弱指针,打破循环引用
    @property(nonatomic, weak)id target;
    
    +(instancetype)proxyWithTarget:(id)target;
    
    @end
    
    @implementation SSProxy
    
    +(instancetype)proxyWithTarget:(id)target
    {
        SSProxy *proxy = [[SSProxy alloc] init];
        proxy.target = target;
        return proxy;
    }
    //消息转发阶段,返回值不为空,直接给某个对象发送消息
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        return self.target;
    }
    
    @end
    
    @interface TestViewController ()
    
    @property(nonatomic,strong)NSTimer *timer;
    
    @end
    
    @implementation TestViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[SSProxy proxyWithTarget:self] selector:@selector(run) userInfo:nil repeats:YES];
    }
    
    -(void)run
    {
        NSLog(@"%s",__func__);
    }
    
    -(void)dealloc
    {
        [self.timer invalidate];
        self.timer = nil;
        
        NSLog(@"%s",__func__);
    }
    
    @end
    
    • 方法三:使用CADisplayLink
    @interface TestViewController ()
    
    @property(nonatomic,strong)CADisplayLink *timer;
    
    @end
    
    @implementation TestViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        self.timer = [CADisplayLink displayLinkWithTarget:[SSProxy proxyWithTarget:self] selector:@selector(run)];
        [self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    }
    
    -(void)run
    {
        NSLog(@"%s",__func__);
    }
    
    -(void)dealloc
    {
        [self.timer invalidate];
        self.timer = nil;
        
        NSLog(@"%s",__func__);
    }
    
    @end
    

    存在的弊端:
    1.NSTimer和CADisplayLink底层是runloop实现的,所以有可能并不准时,如果runloop任务过于繁重,每一圈的处理就会耗时,就导致不准时。
    2.对于CADisplayLink,当CPU忙于其他计算,就无法保证每秒60次的频率执行屏幕绘制。

    GCD定时器

    GCD定时器直接和系统内核挂钩,不依赖于runloop,相对精准。

    @interface TestViewController ()
    
    @property(nonatomic,strong)dispatch_source_t timer;
    
    @end
    
    @implementation TestViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
      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.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        
      dispatch_source_set_event_handler(timer, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
        
      dispatch_resume(timer);
        
      self.timer = timer;
    }
    
    -(void)run
    {
        NSLog(@"%s",__func__);
    }
    
    -(void)dealloc
    {
        NSLog(@"%s",__func__);
    }
    
    @end
    

    相关文章

      网友评论

        本文标题:iOS实战-更精准的定时器

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