定时器
NSTimer
-
创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(testTouchesBegan) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-
使用NSTimer的注意点
- 要保证有一个活跃的runLoop
- NSTimer 必须在同一个线程中申明和释放
- 如果想要NSTimer一直能正常运行, 就必须设置为NSRunLoopCommonModes模式, 防止UI事件阻止定时器执行定时事件. 因为UIScrollView在滑动的时候, runLoop会自动切换到UITrackingRunLoopMode模式, 此刻NSTimer停止, 滑动结束之后, 又会切换到NSDefaultRunLoopMode模式, 此刻并没有开启NSTimer. 碰到这个问题怎么办呢? 我们让NSTimer在两种模式下都可以正常工作就可了, 而NSRunLoopCommonModes是NSDefaultRunLoopMode和UITrackingRunLoopMode两种模式的合集, 设置事件源为这个模式的时候就相当于绑定了集合内的每一个模式.
-
控制器退出的时候, 彻底移除定时器, 防止内存泄露
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:YES]; [self.timerDisplayLink invalidate]; self.timer = nil; }
-
使用NSTimer导致的问题:
- timerWithTimeInterval 方法将target设为A对象的时候, A对象会被timer所持有, 此刻retain一次, 同时timer会被当前的runloop所持有, 又会retain一次.此刻就会导致无法释放.从而产生内存泄露.
- 导致循环引用, time认为当前所在的类在释放的时候, 会进入到dealloc方法, 我们一般在 - (void)dealloc{} 中释放内存并且定时器, 但是当此类释放的时候, 他认为time不会停止计时, 所以不会进入到dealloc方法中.他们就相互等待着一起终老, 终老的这个过程中就循环引用了.
- 上面摘自苹果官方文档对invalidate方法的解释。可以看到,当一个timer被schedule的时候,timer会持有target对象,NSRunLoop对象会持有timer。当invalidate被调用时,NSRunLoop对象会释放对timer的持有,timer会释放对target的持有。除此之外,我们没有途径可以释放timer对target的持有。所以解决内存泄露就必须撤销timer,若不撤销,target对象将永远无法释放。
GCD定时器
GCD定时器不受RunLoop约束,比NSTimer更加准时
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 创建一个定时器 (dispatch_source_t本质还是一个OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置定时器的各种属性
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 5.0 * NSEC_PER_SEC);
dispatch_time_t interval = 2.0 * NSEC_PER_SEC;
dispatch_source_set_timer(self.timer, startTime, interval, 0);
// 设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"-------------------------");
});
// 启动定时器
dispatch_resume(self.timer);
CADisplayLink *timerDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(testLinkTimer)];
self.timerDisplayLink = timerDisplayLink;
[timerDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
timerDisplayLink.frameInterval = 100;
以上定时器的优缺点, 请参考 http://mp.weixin.qq.com/s?__biz=MzAxMzE2Mjc2Ng==&mid=2652155097&idx=1&sn=70a549ca19e3443c5939f2fc7d9c6913&scene=23&srcid=0725xwTVYf8kT34XOLXoMHtI#rd
CADisplayLink
简单地说,它就是一个定时器,每隔几毫秒刷新一次屏幕。
-
CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个runloop中,并给它提供一个 target 和 selector 在屏幕刷新的时候调用。
-
一但 CADisplayLink 以特定的模式注册到runloop之后,每当屏幕需要刷新的时候,runloop就会调用CADisplayLink绑定的target上的selector,这时target可以读到 CADisplayLink 的每次调用的时间戳,用来准备下一帧显示需要的数据。例如一个视频应用使用时间戳来计算下一帧要显示的视频数据。在UI做动画的过程中,需要通过时间戳来计算UI对象在动画的下一帧要更新的大小等等。
-
代码实现
// 创建 CADisplayLink *timerDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(testLinkTimer)]; self.timerDisplayLink = timerDisplayLink; [timerDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; timerDisplayLink.frameInterval = 100; // 释放 [self.timerDisplayLink invalidate]; self.timerDisplayLink = nil;