Swift 4 Xcode 9:API请求无法及时返回数据填充TableView
嘿,我太懂你这种异步请求和TableView加载不同步的头疼了——网络请求是后台异步跑的,而viewDidLoad是主线程同步执行的,所以TableView总是先尝试渲染单元格,这时候数据还没从服务器拿回来,自然就空了。我帮你拆解下问题,再给你靠谱的解决方案:
问题根源
你之前的完成处理程序没生效,大概率是两个原因:要么没在完成回调里触发TableView刷新,要么没切回主线程更新UI(网络请求的回调默认在后台线程,直接操作UI会出问题)。至于信号量,千万别在主线程用wait(),这会直接卡死UI,完全违背iOS的异步设计原则。
正确的完成处理程序用法
先把API请求封装成带完成闭包的函数,确保在数据解析完成后切回主线程触发回调:
// 先定义你的数据模型,比如叫DataItem struct DataItem: Codable { let id: Int let title: String // 其他字段根据你的API返回定义 } // 封装API请求函数,带完成处理程序 func fetchTableData(completion: @escaping ([DataItem]) -> Void) { guard let requestUrl = URL(string: "你的API接口地址") else { completion([]) return } URLSession.shared.dataTask(with: requestUrl) { data, _, error in // 处理网络错误 guard let responseData = data, error == nil else { print("网络请求错误:\(error?.localizedDescription ?? "未知错误")") completion([]) return } // 解析JSON数据 do { let parsedData = try JSONDecoder().decode([DataItem].self, from: responseData) // 必须切回主线程,因为要更新UI相关的操作 DispatchQueue.main.async { completion(parsedData) } } catch { print("数据解析错误:\(error)") completion([]) } }.resume() // 别忘了启动请求! }
然后在viewDidLoad里调用这个函数,拿到数据后刷新TableView:
class YourTableViewController: UITableViewController { // 数据源数组 private var dataSource: [DataItem] = [] override func viewDidLoad() { super.viewDidLoad() // 先注册单元格(如果没在Storyboard里注册的话) tableView.register(YourCustomCell.self, forCellReuseIdentifier: "CellIdentifier") // 发起数据请求 fetchTableData { [weak self] fetchedData in guard let self = self else { return } // 更新数据源 self.dataSource = fetchedData // 刷新TableView self.tableView.reloadData() } } // 实现TableView的数据源方法 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as! YourCustomCell let item = dataSource[indexPath.row] cell.titleLabel.text = item.title // 配置其他单元格内容 return cell } }
更简洁的Swift 5.5+方案:Async/Await
如果你的项目用的是Swift 5.5及以上版本,用Async/Await会让代码更清爽,不用写嵌套闭包:
// 用Async/Await重构请求函数 func fetchTableData() async throws -> [DataItem] { guard let requestUrl = URL(string: "你的API接口地址") else { throw NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的API地址"]) } let (data, _) = try await URLSession.shared.data(from: requestUrl) return try JSONDecoder().decode([DataItem].self, from: data) } // 在viewDidLoad里调用(用Task包裹异步代码) override func viewDidLoad() { super.viewDidLoad() tableView.register(YourCustomCell.self, forCellReuseIdentifier: "CellIdentifier") Task { do { let fetchedData = try await fetchTableData() // 切回主线程刷新UI DispatchQueue.main.async { self.dataSource = fetchedData self.tableView.reloadData() } } catch { print("请求失败:\(error.localizedDescription)") // 这里可以给用户显示错误提示,比如弹出Alert } } }
最后检查这几个关键点
- 确认你在拿到数据后调用了
tableView.reloadData() - 所有更新UI的操作(包括刷新TableView)都必须在主线程执行
- 数据源数组被正确赋值,没有被意外清空或者覆盖
内容的提问来源于stack exchange,提问作者Cameron Reilly
相关产品推荐
相关产品推荐

