NSTimer循环引用

  • Post author:
  • Post category:其他


NSTimer使用不当就会造成内存泄漏,比如常见的使用方法:

//定义
@property (nonatomic, strong) NSTimer *timer;
 
//实现
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(showMsg) userInfo:nil repeats:YES];
 
//销毁
-(void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;
}

由于NSTimer会引用住self,而 self又持有NSTimer对象,所以形成循环引用,dealloc 永远不会被执行,timer 也永远不会被释放,造成内存泄漏。

尝试解决办法:

1、把timer改成弱引用

@property (nonatomic, weak) NSTimer *timer;

虽然self对timer是弱引用,但是控制的delloc方法的执行依赖于timer的invalidate,timer的invalidate又依赖于控制器的delloc方法,依旧是循环引用问题;

2、使用__weak

那换个思路能不能让NSTimer弱引用target:

 __weak typeof(self) weakSelf = self;
 self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(showMsg) userInfo:nil repeats:YES];

weak关键字适用于block,当block引用了块外的变量时,会根据修饰变量的关键字来决定是强引用还是弱引用,如果变量使用weak关键字修饰,那block会对变量进行弱引用,如果没有__weak关键字,那就是强引用。   但是NSTimer的 scheduledTimerWithTimeInterval:target方法内部不会判断修饰target的关键字,所以这里传self 和 weakSelf是没区别的,其内部会对target进行强引用,还是会产生循环引用。

最终解决办法

1、使用苹果提供带block的api

2、类似于工程重写一个定时器(HWWeakTimer),用一个含有weak属性的对象A包裹self作为target,再对A进行消息转发,访问A就相当于访问self,这样就完美的解决了循环引用,且保留了target-action方式。

HWWeakTimer最核心代码:


@interface HWWeakTimerTarget : NSObject

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

@end

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                     target:(id)aTarget
                                   selector:(SEL)aSelector
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats
                                     inMode:(NSRunLoopMode)mode {
    HWWeakTimerTarget* timerTarget = [[HWWeakTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    
    HWWeakTimerParam *param = [[HWWeakTimerParam alloc] init];
    param.userInfo = userInfo;
    param.runLoopMode = mode;
    
    NSTimer *timer = [NSTimer timerWithTimeInterval:interval
                                             target:timerTarget
                                           selector:@selector(fire:)
                                           userInfo:param
                                            repeats:repeats];
    
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:mode];
    timerTarget.timer = timer;
    return timerTarget.timer;
}



版权声明:本文为qq_40319342原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。