关于NSTimer使用的内存泄漏问题之主线程

本文深入探讨了在iOS开发中使用NSTimer时常见的内存泄漏问题,解析了内存泄漏的产生原因,提供了避免和解决内存泄漏的有效策略,包括使用block接口和正确处理repeats属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于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 。
持有关系.jpg

所以不管用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以后的版本。

image.png

/// 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;
}

参考文章:
《IOS定时器操作和NSTimer的各种坑》
《NSTimer的使用》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值