NSTimer
- 作用:文档又讲:在固定的时间间隔被触发,给指定目标发送消息。
- NSTimer使用必须注意点?
- 要想timer能够运行起来,必须将timer实例 添加到 指定线程的Runloop下某个model下. 上面一句话蕴含几点:
1.1. 必须存在线程
1.2. 指定线程的Runloop必须启动
1.3. 必须将timer添加到Runloop的某个model下(即:timer作为Model的item)
- 要想timer能够运行起来,必须将timer实例 添加到 指定线程的Runloop下某个model下. 上面一句话蕴含几点:
- Runloop 与 timer 关系? 文档中是这样写的:
Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated. A nonrepeating timer invalidates itself immediately after it fires. However, for a repeating timer, you must invalidate the timer object yourself by calling its
invalidate
method. Calling this method requests the removal of the timer from the current run loop; as a result, you should always call theinvalidate
method from the same thread on which the timer was installed. Invalidating the timer immediately disables it so that it no longer affects the run loop. The run loop then removes the timer (and the strong reference it had to the timer), either just before theinvalidate
method returns or at some later point. Once invalidated, timer objects cannot be reused.
上面文档提出几个注意点:
- timer必须运行在runloop中,不重复的timer,触发后系统立即使其自身无效. 重复的timer,必须调用invalidate方法才能使timer无效,且调用invalidate方法后会请求从runloop删除timer.
- 使用timer与废弃timer必须在同一个线程中.
- Runloop会在invalidate方法返回之前或之后的某个时间点移除timer(即:移除runloop对timer的强引用)
- 一旦失效,就不能重用计时器对象。
- 根据2中文档所知,runloop会对timer有强引用.
- timer会对目标对象target进行强引用
5.invalidate方法作用? 文档说明:
Stops the timer from ever firing again and requests its removal from its run loop.
//即:停止timer再次被触发,且请求从runloop中移除
NSTimer如何解决循环引用?
-
首选讲NSTimer使用时产生循环引用的原因?
// 实线:强引用,虚线:弱引用
- 可以看出无论self对timer是强/弱引用,timer始终强持有self实例.当self强引用timer时,self与timer相互引用,肯定造成循环.当self弱引用timer时,timer一旦被触发,则timer一定被添加到runloop中,这时runloop强持有timer,timer强持有self,若self是NSObject,UIView,UIViewController的实例,就要求runloop退出(model的item为空)或被释放(线程被销毁),但runloop对应线程是主线程时,就造成self无法被释放.
- 解决方案?
- 直接方法:
2.1.1. 使用iOS10之后新出的block回调方式.
2.1.2.拦截pop方法,废弃timer.不同开发人有不同的方案.我这里是通过自定义navigation,重写pop方法,下沉给nav.toViewController, self实现相应方法,调用timer的invalidate
2.1.3. 在viewDidDisappear里 调用timer的invalidate.(self is UIViewController) - 类方法方案:(强力推荐,因为无需外部调用timer的invalidate,避免遗忘)
就是通过将timer的target设置为一个类对象.虽然相互持有关系没有被打破,可是因为类对象(class object)无需回收,所以不用担心。
- 消息转发方案:
就是通过将timer的target设置为一个NSProxy的实例,自定义继承NSProxy类,实现消息转发方法. 就是说:timer调用target(proxy实例)的selector,利用消息转发原理,回调self的selector
- RunTime方案:
就是随意创建一个object作为timer的selector,给object添加实例方法,添加的实例方法就是timer触发的消息方法.
- Block方案:
即给NSTimer添加分类,添加类方法获取timer实例,将timer的target设置为NSTimer类对象(简介利用了类方法方案),打破timer对self的强持有关系.
具体实现代码https://github.com/zhbgitHub/NSTimerUse
代码中注释有应该的注意点及timer占用资源的释放说明.
- 使用GCD定时器取代NSTimer(较简单直接贴代码)
@interface xxx ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
- (void)startTimer
{
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), 2.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"执行了");
});
});
//开启计时器
dispatch_resume(_timer);
}
- (void)stopTimer
{
dispatch_source_cancel(self.timer);
}