Swift 如何解决Alpha Vantage接口调用后ViewModel无法接收异步响应的问题
问题根因
companyOverviewRequest的didSet只会在「该属性存储的对象引用被替换」时触发,完全无法感知被引用对象的内部属性变化。
你在fetchCompanyOverview中对self.companyOverviewRequest = request的赋值是无效的:callApi()返回的就是当前请求对象本身,引用没有变化,自然不会触发didSet。等异步网络请求完成、AlphaVantageRequest内部的responseJSON被赋值时,ViewModel没有任何订阅机制,自然收不到数据。
对应解决方案
按改动量从小到大排列:
方案1:在请求链路中添加完成闭包
- 给
ApiRequest协议补充回调属性:
protocol ApiRequest { // 保留原有协议定义 var onResponseReceived: (([String: Any]?) -> Void)? { get set } }
- 在
AlphaVantageRequest的responseJSON观察器中触发回调:
class AlphaVantageRequest : ApiRequest { // ... 原有实现 var onResponseReceived: (([String: Any]?) -> Void)? var responseJSON : [String : Any]? { didSet { guard let results = responseJSON else { return } print(results) onResponseReceived?(results) } } }
- 在ViewModel中绑定回调,替换原本无效的
didSet:
class CompanyViewModel: ObservableObject { // 用@Published直接暴露接口数据给视图 @Published var companyOverview: [String: Any]? private var companyOverviewRequest: ApiRequest? private var searchEndpoint: SearchEndpoint init(searchEndpoint: SearchEndpoint) { self.searchEndpoint = searchEndpoint // 初始化请求时绑定回调 let request = CompanyOverviewRequest(symbol: searchEndpoint.symbol) request.onResponseReceived = { [weak self] response in // 切回主线程更新UI状态 DispatchQueue.main.async { self?.companyOverview = response } } self.companyOverviewRequest = request } func fetchCompanyOverview() { companyOverviewRequest?.callApi() } }
方案2:用Combine订阅属性变化,贴合现有ObservableObject设计
- 将
AlphaVantageRequest的responseJSON标记为可发布属性:
class AlphaVantageRequest : ApiRequest { // ... 原有实现 @Published var responseJSON: [String: Any]? }
- 在ViewModel中订阅该属性的变化,替代无效的
didSet:
import Combine class CompanyViewModel: ObservableObject { @Published var companyOverview: [String: Any]? private var cancellable: AnyCancellable? private var companyOverviewRequest: AlphaVantageRequest? private var searchEndpoint: SearchEndpoint init(searchEndpoint: SearchEndpoint) { self.searchEndpoint = searchEndpoint let request = CompanyOverviewRequest(symbol: searchEndpoint.symbol) self.companyOverviewRequest = request // 订阅请求对象的响应变化 cancellable = request.$responseJSON .receive(on: DispatchQueue.main) .sink { [weak self] response in self?.companyOverview = response } } func fetchCompanyOverview() { companyOverviewRequest?.callApi() } }
额外注意点
你之前在fetchCompanyOverview里写的self.companyOverviewRequest = request是完全多余的:因为callApi()返回的就是当前的同一个请求对象,没有引用变化,赋值后不会触发任何逻辑,直接删掉即可。
网络请求的回调运行在子线程,如果你要将响应数据赋值给
@Published属性直接驱动UI,必须切回主线程再更新,否则会出现UI刷新异常的问题。
内容的提问来源于stack exchange,提问作者gavisio
相关产品推荐
相关产品推荐

