Swift MVVM模式下网络请求后ViewController数据为nil问题排查
Swift MVVM异步网络请求导致数据未及时加载的问题
问题核心原因
网络请求是异步操作:postGenericCallVM方法调用后会立即返回,后台才会执行实际的网络请求逻辑,而你在ViewController里调用该方法后立刻打印postDataVM的属性,此时请求还没完成,postDataVM仍处于初始的nil状态,自然打印出nil。
解决方案
下面提供几种常用的解决方式,根据项目需求选择即可:
1. 闭包回调通知数据更新
修改ViewModel的方法,添加完成回调,在请求结束后通知ViewController:
class PostViewModel { var postDataVM: PostGenResult? func postGenericCallVM(completion: @escaping (PostGenResult?, Error?) -> Void) { // 构造你的请求对象 let request = RequestObject( method: .get, // 替换为你的请求方法 urlPath: "your/api/path", // 替换为你的接口路径 isTokenNeeded: true ) APINetworkManager2.sharedInstance.serviceCall(requestObject: request) { [weak self] result in switch result { case .success(let response): self?.postDataVM = response.data // 假设APIResponse包含data字段 completion(self?.postDataVM, nil) case .failure(let error): completion(nil, error) } } } }
ViewController中通过回调获取更新后的数据:
class ViewController: UIViewController { private let postVM = PostViewModel() override func viewDidLoad() { super.viewDidLoad() postVM.postGenericCallVM { [weak self] data, error in guard let self = self else { return } if let error = error { print("请求失败:\(error.localizedDescription)") return } // 此时postDataVM已经被赋值,可以正常访问 print("testing viewmodel data in viewcontroller \(self.postVM.postDataVM?.postCount)") // 这里执行UI更新操作,比如刷新列表 } } }
2. 使用Combine框架(iOS 13+)
通过@Published属性发布数据变化,ViewController订阅该属性的更新:
import Combine class PostViewModel { @Published var postDataVM: PostGenResult? private var cancellables = Set<AnyCancellable>() func postGenericCallVM(){ let request = RequestObject( method: .get, urlPath: "your/api/path", isTokenNeeded: true ) APINetworkManager2.sharedInstance.serviceCall(requestObject: request) .receive(on: DispatchQueue.main) // 切换到主线程更新UI .sink(receiveCompletion: { completion in if case .failure(let error) = completion { print("请求失败:\(error.localizedDescription)") } }, receiveValue: { [weak self] response in self?.postDataVM = response.data }) .store(in: &cancellables) } }
ViewController中订阅数据变化:
import Combine class ViewController: UIViewController { private let postVM = PostViewModel() private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() postVM.postGenericCallVM() // 订阅postDataVM的变化 postVM.$postDataVM .compactMap { $0 } // 过滤掉nil值 .sink { data in print("testing viewmodel data in viewcontroller \(data.postCount)") // 执行UI更新 } .store(in: &cancellables) } }
3. Delegate模式
通过协议定义回调方法,让ViewController作为ViewModel的代理,接收数据更新通知:
// 定义代理协议 protocol PostViewModelDelegate: AnyObject { func postDataDidUpdate(_ data: PostGenResult) func postDataRequestFailed(_ error: Error) } class PostViewModel { weak var delegate: PostViewModelDelegate? var postDataVM: PostGenResult? func postGenericCallVM(){ let request = RequestObject( method: .get, urlPath: "your/api/path", isTokenNeeded: true ) APINetworkManager2.sharedInstance.serviceCall(requestObject: request) { [weak self] result in guard let self = self else { return } switch result { case .success(let response): self.postDataVM = response.data self.delegate?.postDataDidUpdate(self.postDataVM!) case .failure(let error): self.delegate?.postDataRequestFailed(error) } } } }
ViewController实现代理协议:
class ViewController: UIViewController, PostViewModelDelegate { private let postVM = PostViewModel() override func viewDidLoad() { super.viewDidLoad() postVM.delegate = self postVM.postGenericCallVM() } func postDataDidUpdate(_ data: PostGenResult) { print("testing viewmodel data in viewcontroller \(data.postCount)") // 执行UI更新 } func postDataRequestFailed(_ error: Error) { print("请求失败:\(error.localizedDescription)") } }
注意事项
- 处理网络请求回调时,必须使用
[weak self]避免循环引用,防止内存泄漏。 - 所有UI更新操作都要在主线程执行,Combine的
receive(on: DispatchQueue.main)或者手动调用DispatchQueue.main.async都可以实现。
内容的提问来源于stack exchange,提问作者Swift
相关产品推荐
相关产品推荐

