You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 21:05:11