美文网首页
NSTimer的循环引用问题

NSTimer的循环引用问题

作者: Silence_xl | 来源:发表于2021-05-14 20:45 被阅读0次
NSTimer

scheduledTimerWithTimeInterval与timerWithTimeInterval、initWithFireDate的区别

有三种方法来创建一个定时器

1.使用scheduledTimerWithTimeInterval
类方法创建计时器和进度上当前运行循环在默认模式(NSDefaultRunLoopMode)

2.使用timerWithTimerInterval
类方法创建计时器对象没有调度运行循环(RunLoop)
在创建它,必须手动添加计时器运行循环,通过调用adddTimer:forMode:方法相应的NSRunLoop对象

3.使用initWithFireDate
在创建它,必须手动添加计时器运行循环,通过使用addTimer:forMode:方法相应的NSRunLoop对象

- (void)execute {
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}
- (void)execute {
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    //为什么在主线程不需要这句run,那是因为主线程有RunLoop而且已经启动
    [[NSRunLoop currentRunLoop] run];
}

这两个代码效果是一样的,scheduledTimerWithTimeInterval相当于timerWithTimeInterval的两句。

使用过NSTimer的应该都清楚,NSTimer会和调用对象之间循环引用,从而导致内存泄漏。下面我们通过一个小测试,来说明这个问题。我们在一个VC的viewDidLoad方法里开启一个timer,在VC的dealloc方法里停止这个timer,如果没有循环引用,那么当我们退出这个VC之后,会调用VC的dealloc方法,从而停止timer,相关代码如下:

#import "TimerVC.h"

@interface TimerVC ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation TimerVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];
    [self startTimer];
}

- (void)startTimer {
    if (!_timer) {
        _timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(pollingTimer:) userInfo:nil repeats:YES];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    [_timer fire];
}

- (void)stopTimer {
    [self.timer invalidate];
    self.timer = nil;
}

- (void)pollingTimer:(NSTimer *)timer {
    NSLog(@"*****************TimerVC pollingTimer");
}

- (void)dealloc {
    [self stopTimer];
}

@end

运行之后发现,即便退出了TimerVC,其内部的timer仍然没有停止,一直在后台打印log,而且不会调用TimerVC的dealloc方法,这是因为timer和VC相互引用,导致谁也不会被释放。为了解决这个问题,iOS 10中新增了两个API,允许我们使用block的方式执行定时代码,这两个API如下:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;

我们把上面的startTimer方法改成如下方式:

- (void)startTimer {
    if (!_timer) {
        _timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"*****************TimerVC pollingTimer");
        }];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    [_timer fire];
}

重新执行,发现关闭TimerVC之后,stopTimer方法得以执行,说明使用block方式确实能解决循环引用。但使用block的方式也会有两个至关重要的问题:

  1. 我们必须在调用者的dealloc方法里手动停止timer,否则timer会一直执行,并不被释放。
  2. 请找一个只支持ios 10以上的公司。。。
    我们的项目中使用的是另外一种方式,代码在这里
@interface NSTimer (YYAdd)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;

@end

//===============================================================================

@implementation NSTimer (YYAdd)

+ (void)_yy_ExecBlock:(NSTimer *)timer {
    if ([timer userInfo]) {
        void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];
        block(timer);
    }
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
    return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
    return [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}

@end

当我们在项目中比如一个VC里用上述代码初始化Timer时,各对象的引用关系如下: 截屏2021-05-14 下午8.42.29.png

这样,当退出VC时,LifeTracker由于没有持有者而被回收,从而走到了LiferTracker的dealloc方法里,我们在dealloc方法里调用了timer的invalidate方法,并将timer置为nil,这时,timerProxy也没有了持有者,所以也会被释放。整个过程没有内存泄漏,而且调用者不用手动调用timer的invalidate,方便快捷。

最终代码

#import "TimerVC.h"
#import "NSTimer+YYAdd.h"

@interface TimerVC ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation TimerVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];
    [self startTimer];
}

- (void)startTimer {
    if (!_timer) {
        __weak typeof(self) weakSelf = self;
        _timer = [NSTimer scheduledTimerWithTimeInterval:2 block:^(NSTimer * _Nonnull timer) {
            [weakSelf pollingTimer];
        } repeats:YES];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    [_timer fire];
}

- (void)stopTimer {
    [self.timer invalidate];
    self.timer = nil;
}

- (void)pollingTimer {
    NSLog(@"*****************TimerVC pollingTimer");
}

- (void)dealloc {
    [self stopTimer];
    NSLog(@"*****************================");
}

@end

相关文章

  • ios学记0016-循环引用问题的解决方法

    Block,NSTimer循环引用问题的解决方法 摘要:NSTimer, Block, 循环引用, 内存泄漏 注:...

  • 解决NSTimer循环引用的三种方法

    总结一下项目中遇到的NSTimer循环引用问题 上述方法NSTimer作为属性时会产生循环引用问题,weaksel...

  • ios中循环引用问题

    ios中循环引用问题 NO1: NSTimer 问题:当你创建使用NSTimer的时候,NSTimer会默...

  • Controller销毁NSTimer释放的细节

    关于NSTimer释放和内存泄漏的问题。 @(NSTimer)[内存管理,NSTimer释放,循环引用] 首先需要...

  • iOS定时器循环引用分析及完美解决方案

    目录 1.NSTimer导致的循环引用分析2.NSTimer循环引用解决思路误区3.NSTimer循环引用解决方案...

  • NSTimer的循环引用

    NSTimer基本使用 NSTimer与RunLoop NSTimer 循环引用的问题 如何在子线程使用NSTim...

  • NSTimer 循环引用问题

    题记 在iOS 10系统之前,系统的NSTimer是会引起循环引用的,导致内存泄漏。下面就针对这个问题给出几种解决...

  • NSTimer循环引用问题

    项目中可能会用到NSTimer实现计时器功能,但NSTimer处理不当,会造成循环引用,内存泄漏。通常我们使用NS...

  • NSTimer循环引用问题

    在实际的开发中,在需要定时器时,这样的代码很常见。 那么这样代码如果处理不当,很容易引起循环引用问题。 循环引用产...

  • 4-8 循环引用

    3种循环引用 自循环引用 相互循环引用 多循环引用 Block的循环引用 NSTimer 的循环引用 破除循环引用...

网友评论

      本文标题:NSTimer的循环引用问题

      本文链接:https://www.haomeiwen.com/subject/hqkcjltx.html