美文网首页iOS技术相关
iOS NSTimer 循环引用引发的一些思考和思路

iOS NSTimer 循环引用引发的一些思考和思路

作者: Leesim | 来源:发表于2018-02-24 19:02 被阅读29次

在项目里,难免要使用到定时器NSTimer,如果稍微不留意,特别容易造成循环引用。以前的解决方案过于笨拙,现在又对以前的防止循环引用的方法进行了进一步的修改,更符合代码的可维护性。

造成NSTimer循环引用问题的原因

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

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

我们在声明timer属性的时候,使用strong去修饰,那么self就强引用了timer这个属性,在使用timer的时候target又指向self ,timer又强引用了self。这样就造成了循环引用。所以在使用timer的时候,要特别小心留意,如果不解决循环引用问题,后续会造成特别多的内存泄露,连锁反应造成更坏的影响。

以往笨拙的解决方案

以往的解决思路是在控制器消失的时候 关闭计时器

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.timer invalidate];
    self.timer = nil;
}

在控制器出现时候 启动计时器

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}

但是这样过于繁琐,上面的代码只是一个过程说明,根据业务逻辑可能要做大量的状态判断来控制timer的启动和关闭。由于以前项目的时间问题,采用了这种笨拙且不符合代码规范的写法,如果使用这种方法到后期代码维护阶段,特别难拆分和进一步维护。

思考之后优化的解决方案

@interface NSTimer (LMBlockSupport)

+ (NSTimer *)lm_scheduledTimerWithTimeInterval:(NSTimeInterval)time
                                         block:(void(^)(void))block
                                       repeats:(BOOL)repeats;

@end

@implementation NSTimer (LMBlockSupport)

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

+ (void)lm_blockInvoke:(NSTimer *)timer{
    
    void(^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

@end

对NSTimer添加一个分类,在分类内添加一个新的类方法。类方法有一个类型为block的参数(定义的block位于栈上,为了防止块被释放,需要调用copy方法,将块移到堆上)。使用这个类别的方式如下:

__weak TimerViewController *weakSelf = self;
    self.timer = [NSTimer lm_scheduledTimerWithTimeInterval:1.0 block:^{
        __strong TimerViewController *strongSelf = weakSelf;
        [strongSelf timerAction];
    } repeats:YES];
    
    [self.timer fire];

这个方案,就打破了self 和 timer之间的循环引用关系。
__strong TimerViewController *strongSelf = weakSelf主要是为了防止执行块的代码时,类被释放了。
在类的dealloc方法中,记得调用:

-(void)dealloc{
  
    //在dealloc方法内记得关闭计时器
    [self.timer invalidate];
    self.timer = nil;

}

既然是相互的强引用 是不是在target的时候 使用weakself 就可以解决循环引用问题了吧?

这个疑问最初就在我脑子里闪过了一下,但是当你看这个API的属性定义:其中target的说明The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated。就是要强引用这个target。大概是这样的, __strong strongSelf = wself 强引用了一个弱引用的变量,结果还是强引用,也就是说strongSelf持有了wself所指向的对象(也即是self所只有的对象),这和你直接传self进来是一样的效果,并不能达到解除强引用的作用。

Demo

这个分类也在demo里面。看代码的话可能更容易理解一些,可以直接使用里面的分类达到效果。

demo地址

相关文章

网友评论

    本文标题:iOS NSTimer 循环引用引发的一些思考和思路

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