Swift MVVM架构下网络请求成功但数据无法显示问题排查
问题现象
开发基于MVVM的Swift应用时,NewsService中每次网络请求成功都会打印success,但ViewModel中articles的didSet闭包、以及fetchNews闭包里的print(articles)都无输出,表格也无法加载数据。
核心排查点与修复方案
1. JSON解码失败但未捕获错误
你的NewsService中,解码失败时直接return,没有打印错误信息,导致无法定位解码问题——这是最可能的原因:即使走到了print("success"),如果JSON结构和Model不匹配,解码会失败,进而不会调用completion(articles),ViewModel的闭包自然不会执行,也就不会有articles的打印输出。
修复代码:
在NewsService的catch块中添加错误打印:
class NewsService { static let shared = NewsService() func fetchNews(completion: @escaping ([Article]) -> Void) { // 确保这里填入有效的API URL guard let url = URL(string: "你的新闻API地址") else { print("无效的URL") return } let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { print("网络请求错误:\(error)") return } guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { print("响应状态码错误") return } guard let data = data else { print("无返回数据") return } let decoder = JSONDecoder() do { print("success") let articles = try decoder.decode(Response.self, from: data).articles completion(articles) } catch { // 新增:打印解码错误,定位问题 print("JSON解码失败:\(error)") return } } task.resume() } }
通过打印的错误信息,可以快速定位:比如API返回的description是可选值,但你的Article结构体里定义为非可选let description: String;或者JSON字段名与Model属性名不匹配(比如API返回desc而不是description)。
2. URL无效导致请求未发起
你贴出的代码中URL(string: "")是空字符串,这会返回nil,整个请求逻辑不会执行。但你说会打印success,推测是你贴代码时隐藏了真实URL,务必确保填入的是完整、有效的HTTPS地址。
3. ViewModel与ViewController的联动问题
你在ViewController中设置了newsTableVM.delegate = self,但ViewModel中并未定义delegate属性,也没有在articles更新时通知ViewController刷新UI。即使后续数据加载成功,表格也不会自动刷新。
修复代码:
首先在ViewModel中定义delegate协议:
protocol NewsTableViewModelDelegate: AnyObject { func articlesDidUpdate() } class NewsTableViewModel { weak var delegate: NewsTableViewModelDelegate? var articles = [Article]() { didSet { print(articles) // 数据更新时通知ViewController delegate?.articlesDidUpdate() } } func fetchNews() { NewsService.shared.fetchNews { [weak self] articles in guard let self = self else { return } self.articles = articles print(articles) } } }
然后在ViewController中实现协议方法,刷新表格:
class NewsTableVC: UITableViewController, NewsTableViewModelDelegate { // ... 现有代码 func articlesDidUpdate() { DispatchQueue.main.async { self.tableView.reloadData() } } override func viewDidLoad() { super.viewDidLoad() newsTableVM.delegate = self newsTableVM.fetchNews() // 只做表格注册,不要提前reloadData(此时数据还未返回) tableView.register(ArticleCell.self, forCellReuseIdentifier: reuseIdentifier) } // 移除原updateUI方法,或修改为仅做注册逻辑 }
4. 提前调用表格刷新
原ViewController的viewDidLoad中,调用fetchNews()后立即调用updateUI()执行tableView.reloadData(),此时网络请求还未完成,articles是空数组,刷新操作无意义,建议移除这部分提前刷新的逻辑。
内容的提问来源于stack exchange,提问作者guy

