iOS开发:Dispatch Queue填充数组后其他方法访问为空的问题
问题:异步加载API数据后数组为空的解决办法
我从API拉取JSON数据并使用JSONDecoder解码,尝试在Dispatch Queue中填充数组,但在其他方法(比如collectionView的数据源方法)中访问该数组时,数组为空。
我的相关代码:
API请求与解码代码:
if let url = URL(string:"https://parseapi.back4app.com/classes/restaurants"){ var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("app id", forHTTPHeaderField: "X-Parse-Application-Id") request.setValue("REST API key", forHTTPHeaderField: "X-Parse-REST-API-Key") let session = URLSession.shared session.dataTask(with: request) { [self] (data, response, err) in guard let jsonData = data else { return } do { let decoder = JSONDecoder() let restaurantList = try decoder.decode(restaurants.self, from: jsonData) self.restaurantStruct = restaurantList DispatchQueue.main.async { self.fillArray() } } catch let jsonErr { print("Error decoding JSON", jsonErr) } } .resume() }
填充数组的方法:
func fillArray(){ var counter = 0 for anitem in restaurantStruct!.results{ //print("\(counter) : \(anitem)") restaurantArray.insert(["restaurant" : anitem.restaurant, "name" : anitem.name, "description" : anitem.description , "calories" : anitem.calories , "price" : anitem.price , "ImageUrl" : anitem.ImageUrl , "type" : anitem.type], at: counter) counter += 1 } }
CollectionView数据源方法:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if(collectionView == categoryCollectionView){ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! CategoryCollectionViewCell cell.label.text = categories[indexPath.row]["Category"] cell.imageView.image = UIImage(named:categories[indexPath.row]["Image"]!) return cell } else{ let menu = getRestaurantMenu("TFC") print(menu) let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell2", for: indexPath) as! CollectionViewCell return cell } }
问题原因分析:
- 异步请求的时序问题:网络请求是异步执行的,
collectionView的数据源方法会在页面加载时立即调用,此时API请求还未完成,restaurantArray自然为空。 - 数组填充后未刷新UI:
fillArray完成后没有通知collectionView刷新,即使数组已经有数据,UI也不会更新,导致你看到的还是空数组的状态。 getRestaurantMenu调用时机过早:在cellForItemAt中直接调用getRestaurantMenu,此时数组可能还没填充完成,返回结果为空。
解决方案:
1. 填充数组后立即刷新CollectionView
修改fillArray方法,在数组填充完成后调用reloadData()刷新对应的collectionView:
func fillArray(){ restaurantArray.removeAll() // 先清空旧数据,避免重复添加 for (index, anitem) in restaurantStruct!.results.enumerated() { restaurantArray.insert(["restaurant" : anitem.restaurant, "name" : anitem.name, "description" : anitem.description , "calories" : anitem.calories , "price" : anitem.price , "ImageUrl" : anitem.ImageUrl , "type" : anitem.type], at: index) } // 刷新对应的菜单collectionView(替换成你实际的collectionView变量名) menuCollectionView.reloadData() }
2. 调整cellForItemAt的数据源逻辑
不要在cellForItemAt中实时调用getRestaurantMenu,而是直接使用已填充的restaurantArray配置cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if collectionView == categoryCollectionView { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! CategoryCollectionViewCell cell.label.text = categories[indexPath.row]["Category"] cell.imageView.image = UIImage(named: categories[indexPath.row]["Image"]!) return cell } else { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell2", for: indexPath) as! CollectionViewCell // 先判断数组是否有足够数据,避免越界 guard indexPath.row < restaurantArray.count else { return cell } let menuItem = restaurantArray[indexPath.row] // 根据你的cell结构配置内容,示例: // cell.nameLabel.text = menuItem["name"] as? String // cell.priceLabel.text = "\(menuItem["price"] ?? 0)" return cell } }
3. 可选:用回调监听数据加载完成(进阶)
如果需要更灵活的状态监听,可以添加闭包回调,在数据加载完成后触发UI刷新:
// 定义回调闭包 var onDataLoaded: (() -> Void)? // 在API请求的主队列回调中调用 DispatchQueue.main.async { self.fillArray() self.onDataLoaded?() } // 在viewDidLoad中设置回调 override func viewDidLoad() { super.viewDidLoad() self.onDataLoaded = { [weak self] in self?.menuCollectionView.reloadData() } // 发起API请求 fetchRestaurantData() }
内容的提问来源于stack exchange,提问作者Ggg Ggff
相关产品推荐
相关产品推荐

