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

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
    }
    
}

问题原因分析:

  1. 异步请求的时序问题:网络请求是异步执行的,collectionView的数据源方法会在页面加载时立即调用,此时API请求还未完成,restaurantArray自然为空。
  2. 数组填充后未刷新UI:fillArray完成后没有通知collectionView刷新,即使数组已经有数据,UI也不会更新,导致你看到的还是空数组的状态。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 06:30:47