美文网首页
防止内存泄露的NSTimer定时器

防止内存泄露的NSTimer定时器

作者: ImmortalSummer | 来源:发表于2020-03-27 10:38 被阅读0次

目录

  • NSTimer的基础用法

  • NSTimer的内存泄露

  • 安全防侧漏的定时器


NSTimer的基础用法

创建定时器


// 初始化定时器后直接加入到当前运行循环RunLoop中 在TimeInterval后 自动启动
 +(NSTimer *)scheduledTimerWithTimeInterval:ti invocation:invocation repeats:yesOrNo;
 +(NSTimer *)scheduledTimerWithTimeInterval:ti target:(id)aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo;

// 创建但不会加入到runloop中 建立之后 须手动加入
+(NSTimer *)timerWithTimeInterval:ti invocation:invocation repeats:yesOrNo;
+(NSTimer *)timerWithTimeInterval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo;


// 需手动加入RunLoop
self.timer =[NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(scanError) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:self.timer forMode:NSDefaultRunLoopMode];

运行和停止定时器

// 是否在运行
@property (readonly, getter=isValid) BOOL valid;

//停止
[timer setFireDate:[NSDate distantFuture]];
//运行
[timer setFireDate:[NSDate distantPast]];

移除定时器


-(void)clearTimer{
    if(self.timer != nil){
        [self.timer invalidate];
        self.timer = nil;
    }
}

NSTimer的内存泄露

引起内存泄露的原因:

NSTimer要结合NSRunloop来使用, 当timer加入runloop时, 实际上是runloop强引用了timer. 如果我们现在有一个控制器vc(或者视图) 使用了timer, 将timer作为其成员变量, 这个vc强引用了这个timer, runloop一直存在, 所以timer不会释放, 所以这个vc也不会释放. 这样就出现了内存泄露.
实际上, 当你吧vc的timer设置为弱引用(weak), 也是无济于事的. 当你吧timer的target 设置为self (指这个vc)时, timer对这个vc依然是强引用.

/*
 ** 定时器内存泄露原因
 -------------- {一直存在的 runloop} -------------------
                      |||
 -------------- {timer 加入了runloop} ------------------
                      |||
 -------------- {timer作为vc的属性被vc强引用} ------------
 -------------- {vc作为timer的target被timer强引用} ------
 */

安全防侧漏的定时器

参考地址

方法一

当这个控制器在退出时(退出按钮, 点击时执行dismiss或者pop) 这退出前执行 [self.timer invalidate]; 可以释放这个timer, 解决内存泄露问题. 由此我们也可以推测, timer的 invalidate方法应该释放了强引用的target, 退出了runloop.

方法二

写一个安全的定时器方法, 承接timer对self的强引用.

//
//  WSafeTimer.h
//

#import <Foundation/Foundation.h>

@interface WSafeTimer : NSObject
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
@end
//
//  WSafeTimer.m
//

#import "WSafeTimer.h"

@interface WSafeTimer()
@property(nonatomic,weak) NSTimer *timer;
@property(nonatomic,weak) id target;
@property(nonatomic,assign) SEL selector;
@end

@implementation WSafeTimer
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    WSafeTimer *safeTimer = [WSafeTimer new];
    
    safeTimer.target = aTarget;
    safeTimer.selector = aSelector;
    safeTimer.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:safeTimer selector:@selector(timerHandle) userInfo:userInfo repeats:yesOrNo];
    
    return safeTimer.timer;
}

-(void)timerHandle{
    if (self.target && [self.target respondsToSelector:self.selector]) {
        [self.target performSelector:self.selector];
    }else {
        [self.timer invalidate];
    }
}
@end

//
// 使用方法与正常的定时器无差异
//

#import "MySafeView.h"
#import "WSafeTimer.h"

@interface MySafeView()
@property(nonatomic,strong) NSTimer *timer;
@end

@implementation MySafeView


- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
      // 创建定时器, 使用WSafeTimer创建 
        self.timer = [WSafeTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(changeTimer) userInfo:nil repeats:YES];
        [self.timer setFireDate:[NSDate distantPast]];
    }
    return self;
}

-(void)changeTimer{
   NSLog(@"changeTimer run");
}

-(void)clearTimer{
    if(self.timer != nil){
        [self.timer invalidate];
        self.timer = nil;
    }
}

-(void)dealloc{
    NSLog(@"dealloc run");
    [self clearTimer];
}

@end

相关文章

网友评论

      本文标题:防止内存泄露的NSTimer定时器

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