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

关于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的使用》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]: \[runLoop addTimer:myTimer forMode:NSDefaultRunLoopMode\]; //实际上这步是不需要,scheduledTimerWithTimeInterval已经纳入当前线程运行。如果使用timerWithTimeInterval则需要。 引用\[2\]: \[\[NSRunLoop currentRunLoop\] addTimer:_timer forMode:NSDefaultRunLoopMode\]; //_timer = \[NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(timerAction) userInfo:nil repeats:YES\]; 引用\[3\]: GCD的定时器不受RunLoop中Mode的影响(RunLoop内部也是基于GCD实现的,可以根据源码看到), 比如滚动TableView的时候,GCD的定时器不受影响;且比NSTimer更加准时。 问题: NSTimer的mode是什么意思? 回答: NSTimer的mode是指定定时器在运行时所处的运行循环模式。在使用NSTimer时,可以通过指定mode来控制定时器在哪些运行循环模式下运行。比如在引用\[1\]和引用\[2\]中,都使用NSDefaultRunLoopMode作为定时器的运行循环模式。这意味着定时器会在默认的运行循环模式下运行。而GCD的定时器则不受RunLoop中Mode的影响,可以在任何运行循环模式下运行,如引用\[3\]所示。 #### 引用[.reference_title] - *1* [iOS多线程的初步研究(四)-- NSTimer](https://blog.csdn.net/lengshengren/article/details/12905635)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [NSTimer 基本使用和注意事项](https://blog.csdn.net/wutengwei007/article/details/82221069)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值