关于NSTimer的使用我一直处于模棱两可的状态,刚好近期项目中用到NSTimer,所以整理了一些注意事项并分享给大家,如果有不对的地方希望大家能够及时指正,谢谢。
一共写了两篇文章,另一篇是《关于NSTimer使用的内存泄漏问题之子线程》
使用方法这里不再做介绍了,网上的文章很多,同时本文的结尾也推荐了两篇不错的文章供大家参考。
因为在使用过程中,我们碰见最多问题就是内存泄漏问题。本文的主要核心就是围绕内存泄漏问题展开的。
注:本文讨论皆默认为在主线程下,且target为ViewController;本文没有引用循环引用的概念,因为我认为在NSTimer的使用中,产生内存泄漏的原因是没有处理NSTimer与线程以及其线程内runloop的关系,并不是weak or strong的问题。
- NSTimer运行原理的简单介绍
- 内存泄漏如何产生的
- 什么情况下不会内存泄漏
- 如何解决内存泄漏的问题
Part 1. NSTImer运行的简单介绍
其实NSTimer的运行原理很简单,就是两个因素:1.添加到runloop中;2.开启runloop。
scheduledTimerWith方法是创建并加入到当前的runloop中,所以创建时不需要再次添加到当前线程中,如果在主线程创建则可以直接跑起来(因为主线程的runloop是默认开启的),在子线程的话需要手动开启runloop。
timerWith则需手动加入runloop,如果在子线程创建,还需要手动开启当前子线程的runloop(因为子线程不会自动开启runloop)。
Part 2. 内存泄漏如何产生的
前提:当我们不使用block的方式,并且repeats == YES。
当timer加入到runloop后,runloop会持有timer,timer会保留其target对象也就是持有target 。
所以不管用weak还是strong,timer对target都是强引用的,如果没有办法实现弱引用target,那么就必须要通过先释放timer才能够释放target。
所以在dealloc方法中去[timer invalidate],都会造成内存泄漏,无法释放。因为invalidate的作用是将timer从runloop中移除,而target需要等待timer被移除后才能够解除timer对自己的持有,才可以走dealloc,所以这种情况下,不能在dealloc中invalidate。
解决方案:一般情况下可以在viewWillDisappear或viewDidDisappear中释放,可以解除对target的持有,释放target。
Part 3. 什么情况下不会内存泄漏
刚才在第2点中提到了一个前提,这个前提就是不会发生内存泄露的情况,分为以下两种情况。
1. 通过block接口,这是苹果提供的api,为了防止循环引用,但需要iOS10.0以后的版本。
/// Creates and returns a new NSTimer object initialized with the specified block object. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
/// - parameter: timeInterval The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/// Creates and returns a new NSTimer object initialized with the specified block object and schedules it on the current run loop in the default mode.
/// - parameter: ti The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
+ (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));
/// Initializes a new NSTimer object using the block as the main body of execution for the timer. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
/// - parameter: fireDate The time at which the timer should first fire.
/// - parameter: interval The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
2. repeats == NO,此时timer也不会持有target。
Part 4. 如何解决内存泄漏的问题
#pragma mark 1.不需要重复操作,repeats == NO(其实就是起一个可控的延时操作的作用)
// 当timer执行完任务后,系统会自动从当前线程中移除timer,所以不需要手动处理
#pragma mark 2.需要重复操作,即repeats == YES
/*
* 首推block方式。虽然iOS原生api接口提供的block方式目前仅支持iOS10以后的版本,但不用担心,
YYKit已经写好了替代方式,在YYKit中的NSTimer分类NSTimer_YYAdd中有block方法。
只需在dealloc中[timer invalidate]即可。
*/
- (void)dealloc {
[self.timer invalidate];
}
// 如果你习惯用非block的方式,那么就不能在dealloc中invalidate,如Part 2提到的解决方案.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
// 或
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}