使用URLSession时跨类访问products数组为空的问题排查与解决
问题分析与解决方案
嘿,我帮你拆解下这个问题——你遇到的其实是异步网络请求的时序问题,和do/catch代码块完全没关系!
为啥其他类访问products时总是空的?
你用的URLSession.shared.dataTask(with:)是个异步操作:它会在后台线程默默跑网络请求、解析数据,而调用retrieveProductList()的主线程(或者其他线程)不会等它做完,直接就继续执行后续代码了。
举个实际的执行顺序:
- 你在其他类调用
retrieveProductList() - 代码立刻跑去访问
products数组,但这时候后台的网络请求还没返回,数组自然是空的 - 等网络请求完成、数据解析完,闭包里的
print才会输出有内容的数组——但这时候你早就已经尝试访问过空数组了
怎么让其他类能拿到填充好的products?
核心思路是:等数据准备好之后,主动通知调用方,而不是让调用方直接去读products属性。这里给你两种最常用的实现方式:
方案1:用回调闭包(兼容所有Swift版本)
修改你的retrieveProductList方法,加一个完成回调,当数据解析完就把结果传出去:
private let productListUrl = URL(string: "https://api/products.json") var products = [Product]() func retrieveProductList(completion: @escaping ([Product]?, Error?) -> Void) { guard let productListUrl = productListUrl else { completion(nil, NSError(domain: "Invalid URL", code: -1, userInfo: nil)) return } URLSession.shared.dataTask(with: productListUrl) { (data, response, error) in // 先处理网络错误 if let error = error { completion(nil, error) return } guard let data = data else { completion(nil, NSError(domain: "No data received", code: -2, userInfo: nil)) return } do { let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as! [String:Any] // 这里别用as!,改成guard let更安全,防止JSON结构不对崩溃 guard let tempArray = jsonData["products"] as? [[String:Any]] else { completion(nil, NSError(domain: "Invalid JSON structure", code: -3, userInfo: nil)) return } // 用compactMap替代循环,更简洁,还能自动过滤nil的Product self.products = tempArray.compactMap { Product(json: $0) } print("In ProductService: \(self.products)") // 通知调用方数据准备好了 completion(self.products, nil) } catch { print("Error parsing data: \(error)") completion(nil, error) } }.resume() }
然后在其他类里这么调用:
let service = ProductService() service.retrieveProductList { products, error in if let error = error { print("Oops, fetch failed: \(error)") return } guard let products = products else { print("No products found") return } // 这里的products就是填充好的!放心用 print("From other class: \(products)") // 如果要更新UI,记得切回主线程 DispatchQueue.main.async { // 比如刷新你的表格/列表视图 } }
方案2:用Async/Await(Swift 5.5+,更清爽)
如果你的项目用的是Swift 5.5及以上版本,强烈推荐用异步等待语法,代码可读性高很多:
private let productListUrl = URL(string: "https://api/products.json") var products = [Product]() func retrieveProductList() async throws -> [Product] { guard let productListUrl = productListUrl else { throw NSError(domain: "Invalid URL", code: -1, userInfo: nil) } // 用await等待网络请求完成 let (data, _) = try await URLSession.shared.data(from: productListUrl) let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as! [String:Any] guard let tempArray = jsonData["products"] as? [[String:Any]] else { throw NSError(domain: "Invalid JSON structure", code: -3, userInfo: nil) } self.products = tempArray.compactMap { Product(json: $0) } print("In ProductService: \(self.products)") return self.products }
调用的时候要放在异步上下文里(比如Task):
let service = ProductService() Task { do { let products = try await service.retrieveProductList() // 这里拿到的就是完整的products数组 print("From other class: \(products)") // 更新UI要切回主线程,用MainActor.run await MainActor.run { // 刷新UI操作 } } catch { print("Fetch error: \(error)") } }
额外小建议
- 尽量别用
as!强制解包,用guard let或者if let,不然JSON结构一变你的APP就崩溃了 - 如果你的
Product结构体还没实现Codable,建议加上!用JSONDecoder解析比JSONSerialization安全又简洁,比如:let products = try JSONDecoder().decode([Product].self, from: data) - 所有UI操作必须在主线程执行,所以拿到数据后要记得切线程哦!
内容的提问来源于stack exchange,提问作者Tyklonix
相关产品推荐
相关产品推荐

