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

SwiftUI不使用DispatchGroup调用多API时加载器卡顿及逻辑失效问题

问题修复方案

核心问题分析

  1. 异步请求与同步计数不匹配:你在同步循环里直接递增apiCountIndex,这个变量只记录了「发起了多少请求」,和「请求是否完成」完全无关——URLSession.dataTask是异步执行的,循环跑完时所有请求还在后台处理,此时的计数逻辑根本无法判断请求是否全部完成。
  2. 加载器关闭逻辑错误:你把关闭加载器的代码放在了switch的default分支里,但你的4个API对应的item.id是1-4,永远走不到default,所以正常情况下加载器永远不会关闭。
  3. 未处理请求失败场景:如果某个API请求报错,对应的回调里没有任何计数逻辑,会导致总完成数永远达不到4,加载器直接卡住。

修复后的代码

func callApiTest() {
    // 主线程开启加载器(确保UI操作在主线程)
    DispatchQueue.main.async {
        self.isMainLoading.toggle()
    }
    
    print("Start Fetching")
    
    let cohortId = UserDefaults.standard.integer(forKey: "cohortId")
    
    let apiItems = [
        ApiModel(id: 1, apiName: "user/get/x/0?cohort_id=\(cohortId)"),
        ApiModel(id: 2, apiName: "trainings/xx/0?cohort_id=\(cohortId)"),
        ApiModel(id: 3, apiName: "survey/get/xxx/0?cohort_id=\(cohortId)"),
        ApiModel(id: 4, apiName: "user/lowest/xxxx/0?cohort_id=\(cohortId)"),
    ]
    
    // 用局部变量记录已完成的请求数,确保线程安全
    var completedRequests = 0
    let totalRequests = apiItems.count
    
    for item in apiItems {
        URLSession.shared.dataTask(with: getRequestConfig(endPoint: item.apiName)) { [weak self] data, _, error in
            guard let self = self else { return }
            
            defer {
                // 不管请求成功/失败,都递增完成计数
                completedRequests += 1
                // 检查是否所有请求都完成
                if completedRequests == totalRequests {
                    // 主线程关闭加载器
                    DispatchQueue.main.async {
                        self.isMainLoading.toggle()
                        print("DONE CALLING ALL APIS..!")
                    }
                }
            }
            
            if let error = error {
                print("parser error = \(error.localizedDescription)")
                return
            }
            
            guard let data = data else {
                print("No data received for API: \(item.apiName)")
                return
            }
            
            switch item.id {
            case 1:
                do {
                    let decoder = try JSONDecoder().decode(ParticipantModel.self, from: data)
                    let items = decoder.response
                    for item in items {
                        let entity = ParticipantEntity(context: self.dataController.container.viewContext)
                        entity.id = Int64(item.id)
                        entity.first_name = item.first_name
                        entity.last_name = item.last_name
                    }
                    UserDefaults.standard.setValue(items.count, forKey: "pCounts")
                    self.dataController.save()
                    print("Success Saving Participant Data")
                } catch {
                    print(error)
                }
            case 2:
                do {
                    let decoder = try JSONDecoder().decode(TrainingModel.self, from: data)
                    let items = decoder.response
                    for item in items {
                        let entity = TrainingEntity(context: self.dataController.container.viewContext)
                        entity.training_id = Int64(item.training_id)
                        entity.group_name = item.columns.group_name
                        entity.group_id = Int16(item.columns.group_id ?? 0)
                    }
                    self.dataController.save()
                    print("Success Saving Training Data")
                } catch {
                    print(error)
                }
            case 3:
                do {
                    let decoder = try JSONDecoder().decode(ParticipantVisitLogModel.self, from: data)
                    let items = decoder.response
                    for item in items {
                        let entity = ParticipantVisitLogEntity(context: self.dataController.container.viewContext)
                        entity.id = Int16(item.id)
                        entity.visit_id = Int16(item.visit_id)
                        entity.type_of_survey = item.type_of_survey
                        entity.survey_id = Int16(item.survey_id)
                    }
                    self.dataController.save()
                    print("Success Saving Participant Visit Log Data")
                } catch {
                    print(error)
                }
            case 4:
                do {
                    let decoder = try JSONDecoder().decode(GroupModel.self, from: data)
                    let items = decoder.response
                    for item in items {
                        let entity = GroupEntity(context: self.dataController.container.viewContext)
                        entity.id = Int16(item.id)
                        entity.name = item.name
                    }
                    self.dataController.save()
                    print("Success Group Data")
                } catch {
                    print(error)
                }
            default:
                print("DEFAULT: Unknown API ID \(item.id)")
            }
        }.resume()
        print("API Called = \(item.apiName)")
    }
}

关键修复点说明

  • 用completedRequests计数完成请求:局部变量记录每个请求完成(无论成功失败)的数量,确保只有当所有请求都处理完才关闭加载器。
  • defer语句保证计数执行:不管请求成功还是失败,defer里的代码都会执行,避免因报错导致计数遗漏。
  • 主线程更新UI:所有修改isMainLoading的操作都放在DispatchQueue.main.async里,因为URLSession的回调是后台线程,直接修改UI会崩溃或不生效。
  • 移除无效的apiCountIndex判断:直接遍历所有API发起请求,不需要额外的索引判断逻辑。

内容的提问来源于stack exchange,提问作者Carole Malonda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 00:01:37