无法通过ViewModel加载TableView数据,请求技术排查
问题分析与解决方案
Hey,我帮你梳理下代码里的问题,核心是异步请求的状态更新没有通知到UI层,还有几个细节可以优化来避免潜在崩溃风险:
1. 异步请求完成后未触发表格刷新
你的 loadStatusCodes() 方法里调用了异步网络请求,但请求完成赋值 statusCodes 后,并没有通知 ViewController 数据已更新。这就导致网络请求返回数据时,TableView 完全不知道要重新加载数据。
修改方案:给ViewModel添加完成回调
更新 StatusCodeViewModel 的 loadStatusCodes 方法,添加一个闭包来通知外部数据加载完成:
class StatusCodeViewModel { let apiClient = APIClient.shared var statusCodes: [StatusCode] = [] let identifier = "statusCodeCell" init() {} // 添加completion回调,通知外部数据加载完成 func loadStatusCodes(completion: @escaping () -> Void) { apiClient.execute(service: .statusCode) { statusCodes in self.statusCodes = statusCodes completion() // 请求完成后触发回调 } } }
然后在 ViewController 里调用时,通过回调触发主线程的表格刷新(UI操作必须在主线程执行):
override func viewDidLoad() { super.viewDidLoad() viewModel = StatusCodeViewModel() viewModel!.loadStatusCodes { [weak self] in // 切回主线程刷新UI DispatchQueue.main.async { self?.tableView.reloadData() } } }
2. 移除无效的viewModel didSet逻辑
你原来的 viewModel 属性的 didSet 只会在viewModel被赋值的瞬间检查 statusCodes 数量,而此时异步请求还未返回,statusCodes 肯定是空数组,所以这个逻辑完全起不到作用,可以直接删掉:
// 删掉这段无效代码 // var viewModel: StatusCodeViewModel? { // didSet { // if viewModel!.statusCodes.count > 0 { // self.tableView.reloadData() // } // } // } // 改成更安全的可选类型 var viewModel: StatusCodeViewModel?
3. 避免强制解包(!),降低崩溃风险
代码里多处使用 viewModel! 强制解包,如果 viewModel 意外为 nil,会直接导致崩溃。建议用可选绑定或空合运算符来优化:
优化TableView DataSource方法
extension ViewController : UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // 用空合运算符,viewModel为nil时返回0 return viewModel?.statusCodes.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // 可选绑定确保viewModel和索引有效 guard let viewModel = viewModel, indexPath.row < viewModel.statusCodes.count else { return UITableViewCell() // 返回默认cell作为 fallback } // 推荐使用带indexPath的dequeue方法,自动处理cell复用 let cell = tableView.dequeueReusableCell(withIdentifier: viewModel.identifier, for: indexPath) cell.textLabel?.text = viewModel.statusCodes[indexPath.row].title return cell } }
4. 验证网络请求是否真的返回数据
最后,可以在 loadStatusCodes 的闭包里加个打印,确认APIClient是否正确获取到了数据:
func loadStatusCodes(completion: @escaping () -> Void) { apiClient.execute(service: .statusCode) { statusCodes in print("获取到的statusCodes数量:\(statusCodes.count)") // 验证数据是否为空 self.statusCodes = statusCodes completion() } }
修改后的完整代码
StatusCodeViewModel
class StatusCodeViewModel { let apiClient = APIClient.shared var statusCodes: [StatusCode] = [] let identifier = "statusCodeCell" init() {} func loadStatusCodes(completion: @escaping () -> Void) { apiClient.execute(service: .statusCode) { statusCodes in print("数据加载完成,数量:\(statusCodes.count)") self.statusCodes = statusCodes completion() } } }
ViewController
class ViewController: UIViewController { @IBOutlet weak var tableView: UITableView! var viewModel: StatusCodeViewModel? override func viewDidLoad() { super.viewDidLoad() viewModel = StatusCodeViewModel() viewModel?.loadStatusCodes { [weak self] in DispatchQueue.main.async { self?.tableView.reloadData() } } } } extension ViewController : UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { viewModel?.statusCodes.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let viewModel = viewModel, indexPath.row < viewModel.statusCodes.count else { return UITableViewCell() } let cell = tableView.dequeueReusableCell(withIdentifier: viewModel.identifier, for: indexPath) cell.textLabel?.text = viewModel.statusCodes[indexPath.row].title return cell } }
内容的提问来源于stack exchange,提问作者user4422315
相关产品推荐
相关产品推荐

