You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无法通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:47:13