调用NSTimer invalidate并置nil后偶现崩溃问题排查
关于NSTimer invalidate后置nil偶现野指针崩溃的原因分析
问题背景
你提到在主线程初始化NSTimer,调用-[invalidate]后立刻将timer置为nil时,偶现Bad access野指针崩溃,崩溃栈显示在resetTimerAndAnimation方法中触发了objc_msgSend错误。
崩溃日志如下:
#0. Crashed: com.apple.main-thread 0 libobjc.A.dylib 0x1819d4430 objc_msgSend + 16 1 MTXX 0x100d73480 -[MTBDummyWebViewViewController resetTimerAndAnimation] (MTBDummyWebViewViewController.m:135) 2 MTXX 0x100d73828 -[MTBDummyWebViewViewController webView:shouldStartLoadWithRequest:navigationType:] (MTBDummyWebViewViewController.m:176) 3 UIKit 0x18bda4f9c -[UIWebView webView:decidePolicyForNavigationAction:request:frame:decisionListener:] + 300 4 CoreFoundation 0x182779670 __invoking___ + 144 5 CoreFoundation 0x1826586cc -[NSInvocation invoke] + 292 6 CoreFoundation 0x18265d16c -[NSInvocation invokeWithTarget:] + 60 7 WebKitLegacy 0x18b811858 -[_WebSafeForwarder forwardInvocation:] + 156 8 CoreFoundation 0x1827773bc ___forwarding___ + 624 9 CoreFoundation 0x18265d01c _CF_forwarding_prep_0 + 92 10 CoreFoundation 0x182779670 __invoking___ + 144 11 CoreFoundation 0x1826586cc -[NSInvocation invoke] + 292 12 WebCore 0x18a4d34f4 HandleDelegateSource(void*) + 108 13 CoreFoundation 0x18271a2e8 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24 14 CoreFoundation 0x18271a268 __CFRunLoopDoSource0 + 88 15 CoreFoundation 0x182719af0 __CFRunLoopDoSources0 + 204 16 CoreFoundation 0x1827176c8 __CFRunLoopRun + 1048 17 CoreFoundation 0x182637fb8 CFRunLoopRunSpecific + 436 18 GraphicsServices 0x1844cff84 GSEventRunModal + 100 19 UIKit 0x18bc0c2e8 UIApplicationMain + 208 20 MTXX 0x10047f6bc main (main.m:15) 21 libdyld.dylib 0x18215a56c start + 4
你的代码调用方式:
[self.timeoutTimer invalidate]; self.timeoutTimer = nil;
崩溃原因分析
这个偶现崩溃的核心问题出在**NSTimer的生命周期和RunLoop回调的时序上**:
- 当你调用
[self.timeoutTimer invalidate]时,NSTimer会被从RunLoop中移除,但RunLoop的任务队列里可能已经存在待执行的timer回调任务,或者像你的崩溃栈显示的那样,WebKit的回调链中还持有这个timer的旧引用。 - 你在
invalidate后立刻将self.timeoutTimer置为nil,此时timer对象的引用计数变为0,会被系统释放。但如果后续有异步触发的逻辑(比如WebView的导航回调)尝试访问这个已经被释放的timer对象,就会触发objc_msgSend的野指针错误。 - 另外,NSTimer初始化时会对其target(也就是你的ViewController)产生强引用,虽然
invalidate会解除这个强引用,但如果在invalidate的内部逻辑还没完全完成时,你就提前把timer置为nil,也可能导致后续的消息发送到已释放的内存。
简单的修复建议
- 延迟置nil:在调用
invalidate后,通过主队列的异步 dispatch 来延迟置nil,给系统足够的时间完成timer的清理逻辑:
[self.timeoutTimer invalidate]; dispatch_async(dispatch_get_main_queue(), ^{ self.timeoutTimer = nil; });
- 使用弱引用封装:可以自定义一个基于弱引用target的NSTimer封装,避免循环引用和野指针问题,比如用iOS 10+支持的
+[NSTimer scheduledTimerWithTimeInterval:repeats:block:]API,内部自动处理弱引用。
内容的提问来源于stack exchange,提问作者bool chow
相关产品推荐
相关产品推荐

