iOS专题@IT·互联网

iOS定时器NSTimer内存泄露原理分析+解决方案

2018-08-13  本文已影响471人  浮游lb

一、NSTimer简介

二、NSTimer与RunLoop

三、NSTimer内存泄露分析

1.NSTimer引用分析

把NSTimer当做普通对象使用,如下实现定时任务,会出现内存泄露

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

@implementation FYLeakView

// 不会调用
- (void)dealloc {
    NSLog(@"%s", __func__);
    [_timer invalidate];
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor yellowColor];
        
        NSLog(@"%@", self.timer); // 触发定时器创建
    }
    return self;
}

- (void)p_timerAction {
    NSLog(@"%s", __func__);
}

- (NSTimer *)timer {
    if (_timer == nil) {
        _timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(p_timerAction) userInfo:nil repeats:YES];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    return _timer;
}
@end
对象引用关系.png
@interface FYNormalView : UIView
- (void)invalidate;
@end

@implementation FYNormalView
- (void)invalidate {
    [self.timer invalidate];
}

......
@end

@interface FYNormalViewController ()
@property (nonatomic, strong) FYNormalView *normalView;
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation FYNormalViewController
- (void)dealloc {
    NSLog(@"%s", __func__);
    [_normalView invalidate]; // invalidate
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"定时器 避免内存泄漏";
    [self.view addSubview:self.normalView];
    self.normalView.frame = CGRectMake(100, 100, 100, 100);
    
    NSLog(@"%f", self.timer.timeInterval);
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.timer invalidate]; // invalidate
}

- (void)p_timerAction {
    NSLog(@"%s", __func__);
}

- (NSTimer *)timer {
    if (_timer == nil) {
        _timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(p_timerAction) userInfo:nil repeats:YES];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];        
    }
    return _timer;
}

- (FYNormalView *)normalView {
    if (_normalView == nil) {
        _normalView = [[FYNormalView alloc] init];
    }
    return _normalView;
}
@end

2.NSTimer内存泄漏解决方案

@implementation NSTimer (Block)
+ (instancetype)fy_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval
                                     actionBlock:(FYTimerActionBlock)block
                                          repeats:(BOOL)yesOrNo
{
    NSTimer *timer = [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(p_timerAction:) userInfo:block repeats:yesOrNo];
    return timer;
}

+ (instancetype)fy_timerWithTimeInterval:(NSTimeInterval)inTimeInterval
                            actionBlock:(FYTimerActionBlock)block
                            runLoopMode:(NSRunLoopMode)mode
                                 repeats:(BOOL)yesOrNo
{
    NSTimer *timer = [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(p_timerAction:) userInfo:block repeats:yesOrNo];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:mode];
    return timer;
}

+ (void)p_timerAction:(NSTimer *)timer {
    
    if([timer userInfo]) {
        FYTimerActionBlock actionBlock = (FYTimerActionBlock)[timer userInfo];
        actionBlock(timer);
    }
}
@end

/// 客户对象使用Timer
@interface FYSolutionView()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation FYSolutionView

- (void)dealloc {
    NSLog(@"%s", __func__);
    [_timer invalidate];  // View析构时,由内部invalid
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor blueColor];
        [self.timer fy_resumeTimer];
    }
    return self;
}

- (void)p_timerAction {
    NSLog(@"%s", __func__);
}

- (NSTimer *)timer {
    if (_timer == nil) {
        __weak typeof(self) weakSelf = self;
        _timer = [NSTimer fy_timerWithTimeInterval:1 actionBlock:^(NSTimer *timer) {
            [weakSelf p_timerAction];
        } runLoopMode:NSRunLoopCommonModes repeats:YES];
    }
    return _timer;
}
@end
Block方案 对象引用关系.png
@interface FYTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer; // weak
@end

@implementation FYTimerTarget
- (void)timerTargetAction:(NSTimer *)timer {
    
    if (self.target) {
        [self.target performSelectorOnMainThread:self.selector withObject:timer waitUntilDone:NO];
    } else {
        [self.timer invalidate];
        self.timer = nil;
    }
}
@end

@implementation NSTimer (NoCycleReference)
+ (instancetype)fy_timerWithTimeInterval:(NSTimeInterval)interval
                                  target:(id)target
                                selector:(SEL)selector
                                userInfo:(id)userInfo
                             runLoopMode:(NSRunLoopMode)mode
                                 repeats:(BOOL)yesOrNo
{
    if (!target || !selector) { return nil; }
    
    // FYTimerTarget作为替代target,避免循环引用
    FYTimerTarget *timerTarget = [[FYTimerTarget alloc] init];
    timerTarget.target = target;
    timerTarget.selector = selector;
    
    NSTimer *timer = [NSTimer timerWithTimeInterval:interval target:timerTarget selector:@selector(timerTargetAction:) userInfo:userInfo repeats:yesOrNo];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:mode];
    timerTarget.timer = timer;
    return timerTarget.timer;
}
@end
替换Target方案 对象引用关系.png

四、NSTimer使用建议

1.初始化分析

2.延迟定时任务VS重复定时任务

References

上一篇下一篇

猜你喜欢

热点阅读