Swift中移parseJSON至NetworkManager后TableView无数据问题排查
问题描述
我花了好几天找解决方案,想把ViewController里的parseJSON()方法移出去优化代码结构,但移到NetworkManager之后,尽管数据能正常解析,UITableView就是显示不了数据。原来在ViewController里的代码能正常显示解析后的表格数据,试过加reloadData()也没用。
可正常工作的代码
class MainViewController: UIViewController { private var companyModel: CompanyModel? private lazy var employeesTable: UITableView = { let table = UITableView(frame: .zero, style: .grouped) table.register(UITableViewCell.self, forCellReuseIdentifier: "cell") return table }() override func viewDidLoad() { super.viewDidLoad() parseJSON() view.addSubview(employeesTable) employeesTable.frame = view.bounds employeesTable.delegate = self employeesTable.dataSource = self } private func parseJSON() { guard let url = URL(string: "https://run.mocky.io/v3/1d1cb4ec-73db-4762-8c4b-0b8aa3cecd4c") else { return } do { let jsonData = try Data(contentsOf: url) companyModel = try JSONDecoder().decode(CompanyModel.self, from: jsonData) } catch { print(error) } }
移至NetworkManager后失效的代码
import Foundation class NetworkManager { var companyModel: CompanyModel? func parseJSON() { guard let url = URL(string: "https://run.mocky.io/v3/1d1cb4ec-73db-4762-8c4b-0b8aa3cecd4c") else { return } do { let jsonData = try Data(contentsOf: url) companyModel = try JSONDecoder().decode(CompanyModel.self, from: jsonData) } catch { print(error) } } } import UIKit class MainViewController: UIViewController { var networkManager = NetworkManager() private var companyModel: CompanyModel? private lazy var employeesTable: UITableView = { let table = UITableView(frame: .zero, style: .grouped) table.register(UITableViewCell.self, forCellReuseIdentifier: "cell") return table }() override func viewDidLoad() { super.viewDidLoad() networkManager.parseJSON() view.addSubview(employeesTable) employeesTable.frame = view.bounds employeesTable.delegate = self employeesTable.dataSource = self } }
问题原因
数据未同步到ViewController的数据源
NetworkManager内部维护了自己的companyModel实例,但ViewController的companyModel依然是空的——而TableView的数据源绑定的是ViewController的companyModel,自然没有数据可以显示。同步网络请求的隐患
你用的Data(contentsOf:)是同步网络请求,会阻塞主线程,这本身就是不规范的开发写法。虽然当前代码中同步请求会立即执行,但实际场景下异步请求才是正确选择,而异步请求会导致你调用parseJSON()后,数据还没解析完成就已经初始化了TableView,后续也没有触发刷新。
修复方案
推荐方案:用闭包回调实现数据传递(解耦+异步)
修改NetworkManager,让它通过闭包返回解析结果,同时改用异步URLSession请求:
class NetworkManager { func fetchCompanyData(completion: @escaping (CompanyModel?, Error?) -> Void) { guard let url = URL(string: "https://run.mocky.io/v3/1d1cb4ec-73db-4762-8c4b-0b8aa3cecd4c") else { let error = NSError(domain: "NetworkError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的请求URL"]) completion(nil, error) return } URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { completion(nil, error) return } guard let data = data else { let error = NSError(domain: "NetworkError", code: -2, userInfo: [NSLocalizedDescriptionKey: "未获取到数据"]) completion(nil, error) return } do { let companyModel = try JSONDecoder().decode(CompanyModel.self, from: data) completion(companyModel, nil) } catch { completion(nil, error) } }.resume() } }
然后修改ViewController的调用逻辑,拿到数据后更新模型并刷新TableView:
override func viewDidLoad() { super.viewDidLoad() // 先配置TableView view.addSubview(employeesTable) employeesTable.frame = view.bounds employeesTable.delegate = self employeesTable.dataSource = self // 请求数据并更新UI networkManager.fetchCompanyData { [weak self] model, error in guard let self = self else { return } if let error = error { print("数据请求失败:\(error)") return } // 更新ViewController的模型 self.companyModel = model // 回到主线程刷新TableView(网络请求在后台线程) DispatchQueue.main.async { self.employeesTable.reloadData() } } }
备选方案(不推荐,耦合性高)
如果不想修改闭包,可以直接复用NetworkManager的模型,但这种方式会让ViewController和NetworkManager强耦合,不利于后续维护:
override func viewDidLoad() { super.viewDidLoad() networkManager.parseJSON() // 直接赋值NetworkManager的模型 self.companyModel = networkManager.companyModel view.addSubview(employeesTable) employeesTable.frame = view.bounds employeesTable.delegate = self employeesTable.dataSource = self // 手动刷新表格 employeesTable.reloadData() }
注意:这种方式仅适用于同步请求,异步请求下数据还没解析完成,companyModel依然是空的,所以不推荐。
额外检查点
确保你的UITableView数据源方法正确使用了companyModel的数据,例如:
extension MainViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return companyModel?.employees.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = companyModel?.employees[indexPath.row].name return cell } }
内容的提问来源于stack exchange,提问作者ofigensky

