Swift中RunLoop阻塞TableView问题:滚动时数值停止更新求助
问题根源分析
你碰到的这个情况本质是RunLoop模式切换导致的:当UITableView滚动时,主线程的RunLoop会切换到UITrackingRunLoopMode(iOS 10+后归属于NSRunLoopCommonModes下的跟踪模式),而默认创建的定时器只绑定在NSDefaultRunLoopMode下——这就导致滚动时定时器暂停触发,哪怕你尝试关联RunLoop,大概率是没加对模式。
下面给你几个针对性的解决方案,从修复现有代码到优化架构都有:
方案1:修复tableTimer的RunLoop绑定模式
如果你想继续用现有定时器方案,只需要把tableTimer加入NSRunLoopCommonModes,这样不管是默认模式还是滚动的跟踪模式,定时器都会正常触发:
Objective-C 代码示例
// 创建tableTimer后,替换原有的RunLoop绑定代码 [self.tableTimer addTimer:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
Swift 代码示例
// 创建tableTimer后执行 RunLoop.current.add(tableTimer, forMode: .common)
方案2:用CADisplayLink替代NSTimer(更适合UI刷新场景)
因为你是要实时更新UI,CADisplayLink是跟屏幕刷新率同步的,而且默认就会在Common Modes下运行,不仅能解决滚动暂停的问题,还能避免0.1秒定时器和屏幕帧率不同步导致的卡顿:
Objective-C 代码示例
// 在TableViewController的viewDidLoad里创建CADisplayLink self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateVisibleCells)]; [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // 对应的单元格刷新方法 - (void)updateVisibleCells { NSArray<UITableViewCell *> *visibleCells = self.tableView.visibleCells; for (UITableViewCell *cell in visibleCells) { // 从AppDelegate获取计数变量并更新UI AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; cell.textLabel.text = [NSString stringWithFormat:@"%ld", (long)appDelegate.countVariable]; } } // 记得在控制器销毁时停止displayLink - (void)dealloc { [self.displayLink invalidate]; }
方案3:优化数据传递逻辑(彻底摆脱定时器轮询)
其实你可以用KVO或者通知中心来替代定时器轮询,让AppDelegate的计数变量变化时主动通知TableViewController更新,这样更高效也更优雅:
用KVO实现的示例(Objective-C)
- 先在AppDelegate里把计数变量声明为可观察属性:
@property (atomic, assign) NSInteger countVariable;
- 在TableViewController里监听这个变量的变化:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; [appDelegate addObserver:self forKeyPath:@"countVariable" options:NSKeyValueObservingOptionNew context:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; [appDelegate removeObserver:self forKeyPath:@"countVariable"]; } // KVO回调,实时刷新可见单元格 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"countVariable"]) { [self.tableView reloadRowsAtIndexPaths:self.tableView.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationNone]; } }
这样只要AppDelegate里的计数变量一变化,TableViewController就会立刻收到通知并刷新UI,完全不需要定时器。
注意事项
- 不管用哪种定时器方案,一定要在控制器销毁时调用
invalidate方法,避免内存泄漏; - 用KVO时必须在视图消失或控制器销毁时移除监听,否则会引发崩溃。
内容的提问来源于stack exchange,提问作者Riccardo Perego
相关产品推荐
相关产品推荐

