Swift中SearchController搭配TableView点击cell触发Index out of range错误求解
崩溃根因分析
核心问题点
- 异步请求和UI刷新时序错位:当前代码调用
fetchKeywords发起网络请求后立刻执行reloadData,但网络请求是异步执行的,此时ViewModel的results数组还未更新。等到请求成功回调中修改results后,没有触发UI刷新,UITableView记录的行数和results实际长度不一致,用户点击超出新数组长度的行就会触发越界崩溃。 - 数组下标无边界校验:
searchKeyword、selectedKeyword方法中直接使用下标访问数组,没有判断下标是否在合法范围内,只要下标超出数组长度必然崩溃。 - 协议方法重复实现:你重复写了两次
UISearchResultsUpdating协议的扩展,两个方法逻辑相似但刷新的视图对象不同(一个刷searchTableView,一个刷searchResults),会导致逻辑混乱、重复发起请求,进一步加大了时序错乱的概率。 - 无搜索防抖和请求去重:输入每个字符都触发请求,会出现后发的请求先返回、旧请求结果覆盖新请求结果的问题,进一步加剧数据和UI的不一致。
修复方案
1. 修正异步刷新逻辑
网络请求成功更新results后,再通知ViewController刷新UI,所有UI操作必须切回主线程执行:
ViewModel修改
// 新增结果更新回调 var onResultsUpdated: (() -> Void)? func fetchKeywords(searchKeyword: String) { self.useCase.fetchKeywords(searchKeyword: searchKeyword, paginationCount: 5) { [weak self] error, result in guard let self = self else { return } if let error = error { self.error = error } if let result = result { self.results = result.searchKeywords } // 切主线程通知UI刷新 DispatchQueue.main.async { self.onResultsUpdated?() } } }
ViewController修改
初始化ViewModel时绑定回调:
viewModel?.onResultsUpdated = { [weak self] in self?.searchTableView.reloadData() }
删除updateSearchResults中立刻刷新的代码:
extension ViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { guard let text = searchController.searchBar.text?.lowercased() else { return } self.viewModel?.fetchKeywords(searchKeyword: text) // 删掉这里的reloadData,移到ViewModel的回调里执行 } }
2. 增加数组下标边界校验
修改ViewModel中两个下标访问的方法,先判断下标合法性再取值:
func searchKeyword(indexPath: Int) -> String? { guard let results = results, indexPath >= 0, indexPath < results.count else { return nil } return results[indexPath].name } func selectedKeyword(at indexPath: Int) -> Keyword? { guard let results = self.results, indexPath >= 0, indexPath < results.count else { return nil } return results[indexPath] }
3. 清理重复代码
直接删掉第二个重复实现UISearchResultsUpdating的扩展(也就是包含updateResults方法的那个扩展),避免逻辑冲突。
4. 增加搜索防抖
减少频繁请求,降低时序错乱概率,在ViewController中增加定时器实现防抖:
// 增加防抖定时器属性 private var searchTimer: Timer? extension ViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { guard let text = searchController.searchBar.text?.lowercased() else { return } // 取消之前的待执行请求 searchTimer?.invalidate() // 输入停止300ms后再发起请求 searchTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { [weak self] _ in self?.viewModel?.fetchKeywords(searchKeyword: text) } } }
内容的提问来源于stack exchange,提问作者panther222128
相关产品推荐
相关产品推荐

