利用类扩展解决NSTimer会保留目标对象

1,903 阅读1分钟

场景

在程序开发中经常会遇到计时器, 比如促销活动的倒计时,发送短信验证码过段时间才允许第二次发送、设置一段倒计时。

问题

这时会用到 下面这个api, scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: API_AVAILABLE(ios(2.0)), 这个api从ios 2.0就开始提供了,可以兼容较低版本的ios系统。可是由于Timer会保留target参数,所以这个api会比较容易造成循环引用。想象一下有一个自定义ViewController1,里边有个倒计时功能,很容易通过下边的代码实现:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(invokeTimer) userInfo:nil repeats:YES];
}

- (void)invokeTimer {
    NSLog(@"%@", self);
}
- (void)dealloc
{
    [_timer invalidate];
    NSLog(@"%s", __FUNCTION__);
}

每过一秒钟触发一次,触发方法会输出log,在dealloc中将timer置为无效。看似天衣无缝,但实际上,从自定义ViewController1返回时,并不会调用dealloc,原因就是上文中提到的NSTimer对target的保留。这样就会造成ViewController1的内存泄漏了。

解决思路

利用像下边这样的类扩展,此时Timer的target 不再是自定义ViewController1

typedef void (^TimerHandler) (NSTimer *);

@interface NSTimer (HandleRetainTarget)

@end

@implementation NSTimer(HandleRetainTarget)

+ (NSTimer *)Eoc_timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))handler  {
    return [NSTimer scheduledTimerWithTimeInterval:interval target:[self class] selector:@selector(repeatTimer:) userInfo:[handler copy] repeats:YES];
}

+ (void)repeatTimer:(NSTimer *)timer {
    TimerHandler handler = timer.userInfo;
    if (handler) {
        handler(timer);
    }
}

@end

调用示例

__weak typeof (self) weakself = self;
_timer = [NSTimer Eoc_timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer *timer) {
     [weakself invokeTimer];
}];

Demo Git 地址

截图

点击Button -> 点击Back -> 查看log

!