Swift远程搜索TableView时数组为空致崩溃问题求助
解决UITableView搜索时的崩溃问题
这是个iOS开发中很常见的异步数据与UI不同步导致的崩溃问题,我给你梳理几个实用的解决思路,从快速修复到体验优化都有:
1. 立即添加边界检查(快速阻断崩溃)
最直接的防护手段,在cellForRowAt方法里先确认索引是否在数据源数组的有效范围内,避免越界访问:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "TeamSearchResultsCell", for: indexPath) as! TeamSearchResultsCell // 先做边界校验,确保索引合法 guard indexPath.row < self.teams.count else { // 返回一个空内容的cell,避免崩溃 cell.TeamNameLable.text = "" return cell } cell.TeamNameLable.text = self.teams[indexPath.row].teamName return cell }
这个修改能立刻阻止崩溃,即使UI和数据源暂时不同步,也会优雅处理而不是闪退。
2. 确保TableView行数与数据源严格绑定
检查你的numberOfRowsInSection方法,必须直接返回teams数组的元素数量,绝对不能写固定值或者其他无关逻辑:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.teams.count }
这是保证TableView展示行数和数据源匹配的基础,从根源减少索引越界的可能。
3. 优化异步回调的数据源更新逻辑
现在的代码是先清空数组、再填充数据、最后刷新UI,这个过程中会有“数组为空但TableView还没刷新”的窗口。可以改成先构建新数组,再原子性替换旧数组并刷新,减少中间异常状态:
loadingJSON("APIPATH", postString:"STRING") { parseJSON in if String(describing: parseJSON) == "-1" { print("No Internet") // 无网络时也要同步更新数据源并刷新UI DispatchQueue.main.async { self.teams.removeAll() self.tableView.reloadData() } } else { if let teamsDictArray = parseJSON["teams"] as? [[String: AnyObject]] { // 先在回调里构建完整的新数据数组 var newTeams = [Team]() // 假设你的数据模型是Team for teamDict in teamsDictArray { // 解析字典到模型 if let teamName = teamDict["teamName"] as? String { newTeams.append(Team(teamName: teamName)) } } // 回到主线程,一次性替换数据源并刷新UI DispatchQueue.main.async { self.teams = newTeams self.tableView.reloadData() } } } }
这样就避免了“数组清空后还没填充数据”的尴尬状态,TableView只会看到空数组或者完整的新数组。
4. 增强节流逻辑:取消旧的网络请求
现在的节流只取消了延迟执行的searchTeams调用,但如果之前的网络请求已经发出去,它的回调还是会回来覆盖新结果。可以给每个搜索请求加个任务标识,取消旧请求:
// 定义一个变量保存当前的搜索请求任务 var currentSearchTask: URLSessionTask? func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { // 取消之前延迟执行的搜索任务 NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(searchTeams), object: nil) // 取消正在进行的网络请求 currentSearchTask?.cancel() // 延迟0.3秒执行新搜索 perform(#selector(searchTeams), with: nil, afterDelay: 0.3) } @objc func searchTeams() { // 发起新请求前再次取消旧请求(双重保险) currentSearchTask?.cancel() // 保存新的请求任务 currentSearchTask = loadingJSON("APIPATH", postString:"STRING") { parseJSON in // ... 原有的回调处理逻辑 } }
然后修改loadingJSON函数,让它返回创建的任务:
func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) -> URLSessionTask { guard Reachability.isConnectedToNetwork() else { completionHandler("-1" as AnyObject) // 返回一个已取消的空任务,避免nil问题 let emptyTask = URLSession.shared.dataTask(with: URL(string: "")!) emptyTask.cancel() return emptyTask } let request = NSMutableURLRequest(url: URL(string: FinalVars.getMobileRequestURL() + link)!) request.httpMethod = "POST" request.timeoutInterval = FinalVars.getRequestTimeoutIntervalInSeconds() request.httpBody = postString.data(using: .utf8) let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in // ... 原有的网络回调逻辑 } task.resume() return task }
这个修改能确保只有最新的搜索请求结果会更新UI,避免旧数据干扰,进一步减少数据源异常的场景。
内容的提问来源于stack exchange,提问作者Marco Weber
相关产品推荐
相关产品推荐

