UIrunloop

iOS如何正确的使用定时器

2018-10-17  本文已影响101人  Mr_DML

在日常的开发中,定时器的使用是不可或缺的,在iOS中主要使用NSTimer,CADisplayLink以及dispatch_source_t来实现定时器,那么我们该如何以正确的姿态来使用我们的定时器呢?

NSTimer, CADisplayLink

问题:定时器无法释放

在导航控制器中创建定时器,每秒执行一次.

@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *link;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) dispatch_source_t dispatchTimer;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
        // NSTimer
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        // CADisplayLink
        self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayAction)];
        [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    
}

- (void)timerAction{
    NSLog(@"timerAction->%s",__func__);
}

- (void)displayAction{
    NSLog(@"displayAction->%s",__func__);
}

- (void)dealloc{
    NSLog(@"已销毁");
    if (self.timer != nil) {
        [self.timer invalidate];
        self.timer = nil;
    }
}
输出
现象:

当点击导航栏左上角的Home按钮后,定时器依然在疯狂的输出,并且控制器中的dealloc方法并未执行.

原因:

出现这样的现象原因是在我们初始化以上两种定时器时需要传入target一个id类型的参数.在NSTimer,CADisplayLink内部如果有一个strong类型的属性接收这个参数就会出现强引用的现象,那么就会导致Timer强引用着控制器,控制器强引用Timer导致循环引用的现象.

循环引用.png
解决方案:
- (void)viewDidLoad {
    [super viewDidLoad];
       __weak typeof(self)WeakSelf = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%s",__func__);
            [WeakSelf timerAction];
        }];

}

- (void)timerAction{
    NSLog(@"timerAction->%s",__func__);
}
- (void)dealloc{
    NSLog(@"已销毁");
    if (self.timer != nil) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

这个时候当你点击导航栏上左侧的Home初你会看到定时器停止了,dealloc也被执行了

输出.png
@interface MLProxy ()
@property (nonatomic, weak) id target;
@end

@implementation MLProxy

+ (instancetype)createProxyWeithTarget:(id)target{
   MLProxy *proxy = [self alloc];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return self.target;
}

@end
- (void)viewDidLoad {
    [super viewDidLoad];
    self.proxy = [MLProxy createProxyWeithTarget:self];
    // NSTimer
    // 由于iOS的消息机制(objc_msgSend()), 系统会对self.proxy 发送一个scheduledTimerWithTimeInterval的消息由于MLProxyb并没有实现该方法,就会执行Runtime的消息转发机制。
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(timerAction) userInfo:nil repeats:YES];
}

输出

这时在尝试一下,可以看到一切正常了,原理:中间代理的出现,就不会出现循环引用的现象了,只要控制器一旦销毁,所有的引用都会解除,所有的对象也都会被释放。


引用关系.png

注意点:NSTimer,CADisplayLink,在底层是运用的RunLoop(运行循环)机制实现的.

RunLoop.png
当在RunLoop处理很多耗时事务时可能会导致定时器的不准确。

问题:综上所述,我们在日常开发中到底有没有更准确的、方便的、注意点少的方法来使用我们的定时器呢?
答案:有的,那就是使用我们GCD中的dispatch_source_t.原因是GCD中的dispatch_source_t定时器不依赖RunLoop。

dispatch_source_t

- (void)viewDidLoad {
    [super viewDidLoad];
  
    self.dispatchTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    // 从此刻开始,每一秒执行一次
    dispatch_source_set_timer(self.dispatchTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(self.dispatchTimer, ^{
        NSLog(@"%s",__func__);
    });
    dispatch_resume(self.dispatchTimer);
}

突然看到这一堆代码,好像也不是那么容易,但是现在的XCode很智能,只要你敲下dispatch_source timer 就会看到下图展示,直接给你生成上述一堆代码。

code.png
接下来就可以愉快的使用定时器了.
上一篇下一篇

猜你喜欢

热点阅读