iOS NSTimer 不走的问题#
背景#
这个版本上线后,突然发现埋点数据直线下降,调试后发现是定时器上传的方法没有走,但是定时器的方法本期并没有修改过。代码如下
- (BOOL)initTimer() {
self.uploadTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(handleUpload) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.uploadTimer forMode:NSRunLoopCommonModes];
}
排查#
这个 handleUpload 方法,怎么都不会走,但是在之前的版本中就是好的,排查了之后发现,是外层调用的地方加了一层异步。即调用的地方变成了
dispatch_async(dispatch_get_global_queue(0, 0), ^{
initTimer()
});
然后就导致了定时器没有启动。
原因#
iOS 是通过 runloop 作为消息循环机制,主线程默认启动了 runloop,可是自线程没有默认的 runloop,因此,我们在子线程启动定时器是不生效的。
解决方法:在子线程启动一下 runloop 即可
- (BOOL)initTimer() {
self.uploadTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(handleUpload) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.uploadTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
}
思考#
通过这个问题,有两点收获,
- timer 在 iOS 开发中经常使用,在很多博客中都看到关于 timer 要注意的地方也很多,通常是内存管理,timer 启动相关,但是在开发中,如果没有真正遇到问题,没有 “疼” 在自己身上,就自己注意的就不够,经过这次之后,相信以后再要在异步使用 timer,肯定会留心很多。
- 这个地方 initTimer 其实是一个 SDK 初始化的类,其实 SDK 内部没有修改,但是外层使用 SDK 的 App 调用修改了,就导致了 SDK 不能正常工作。所以封装 SDK 时要注意,如果使用 timer,要么做线程检查,要么就直接把代码安全保证好;因为不能保证第三方调用者使用时的情况,所以要保证自己代码的正确性。