如何使用Combine将API响应传递至UIViewController并更新CollectionView
问题分析与解决方案
核心问题
你的SearchAPI.fetchData方法中,Future的promise在网络请求完成之前就被调用了。因为URLSession.dataTask是异步操作,当你返回Future时,searchArray还是初始的空数组,网络请求的回调后续才会执行,这就导致sink接收的value始终为空。
修正后的代码
1. 修复SearchAPI类
import Foundation import Combine class SearchAPI { static let shared = SearchAPI() func fetchData(url: String, category: String, queryString: String) -> Future<[Any], Error> { return Future { promise in guard let url = URL(string: url) else { promise(.failure(NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的URL"]))) return } var request = URLRequest(url: url) request.setValue("application/json", forHTTPHeaderField: "Accept") request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.httpMethod = "GET" let task = URLSession.shared.dataTask(with: request) { data, response, error in // 处理网络错误 if let error = error { promise(.failure(error)) return } guard let data = data else { promise(.failure(NSError(domain: "NoData", code: -2, userInfo: [NSLocalizedDescriptionKey: "未获取到数据"]))) return } do { // 用Swift原生Array替代NSArray,更符合Swift习惯 if let responseJson = try JSONSerialization.jsonObject(with: data, options: []) as? [Any] { DispatchQueue.main.async { promise(.success(responseJson)) } } else { promise(.failure(NSError(domain: "InvalidJSON", code: -3, userInfo: [NSLocalizedDescriptionKey: "JSON格式不符合预期"]))) } } catch { promise(.failure(error)) } } task.resume() } } }
2. 调整UIViewController中的代码
把models的类型改成Swift原生[Any](如果API返回的是特定结构,建议定义对应的Model类,用Codable解析会更安全):
// UIViewController中 private var models: [Any] = [] @objc func searchItem(){ moviesView.alpha = 0 albumsView.alpha = 0 booksView.alpha = 0 lblSrchResults.alpha = 1 guard let searchText = txtSearch.text else { return } queryString = searchText.replacingOccurrences(of: " ", with: "%20") print("who is calling ", queryString) let url = "https://endpoint?category=\(category)&query=\(queryString)" SearchAPI.shared.fetchData(url: url, category: category, queryString: queryString) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { completion in switch completion { case .finished: print("finished") case .failure(let error): print(error.localizedDescription) } }, receiveValue: { [weak self] value in print("value ", value) self?.models = value self?.searchCollectionView.reloadData() }).store(in: &observers) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { print("Fetched ", models.count) return models.count }
额外优化建议
- 避免使用
NSArray,改用Swift原生的Array,类型更安全。 - 建议为API返回的数据定义对应的Model结构体,使用
Codable协议解析,替代JSONSerialization,避免类型转换风险,代码更易维护。 - 处理URL创建失败的情况时,不要用
fatalError,而是通过promise返回错误,让上层可以捕获并处理。 - 搜索时可以添加防抖处理,避免用户每输入一个字符就发起一次请求,提升性能。
内容的提问来源于stack exchange,提问作者chevi99
相关产品推荐
相关产品推荐

