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

Swift中group.leave()触发EXC_BREAKPOINT错误排查求助

EXC_BREAKPOINT 在 DispatchGroup.leave() 崩溃分析与修复

错误根源分析

你遇到的EXC_BREAKPOINT(子码7/3/2)崩溃,本质是DispatchGroup的enter()与leave()调用次数不匹配,导致系统触发SIGTRAP信号。结合代码来看,核心问题有两个:

  1. 重复任务发起与共享Group冲突:
    viewDidLoad中同时调用了getCategoriesFromCache()和fetchRecipeData(),两个函数都会遍历tags数组处理数据。fetchRecipeData直接对所有tag发起API请求并调用group.enter(),而getCategoriesFromCache在缓存为空时也会发起API请求并复用同一个全局DispatchGroup,导致部分tag被重复发起请求,进而出现enter()调用次数多于预期的情况。如果其中某个请求的回调因self提前释放而跳过leave(),就会打破Group的计数平衡。

  2. 未处理的缓存读取流程:
    在getCategoriesFromCache()的缓存成功分支中,仅更新UI但未匹配fetchRecipeData()中已调用的group.enter();缓存失败分支也未发起API请求来补充任务,导致部分enter()没有对应的leave(),最终引发Group计数异常。

新增的PersistenceManager.updateWith异步操作本身不会直接导致崩溃,但它延长了回调内的执行逻辑,增加了self提前释放的概率,间接触发了计数不匹配的问题。

修复方案

1. 合并数据加载逻辑,避免重复请求

删除viewDidLoad中重复的fetchRecipeData()调用,仅保留getCategoriesFromCache(),让它统一处理缓存读取与API请求的逻辑。

2. 使用独立DispatchGroup隔离任务

为每个数据加载流程创建独立的DispatchGroup,避免全局Group被多个任务共享导致的计数混乱。

3. 严格保证enter()与leave()配对

使用defer语句确保无论请求成功/失败、self是否存活,leave()都会被调用,彻底避免计数不匹配。

修正后的代码示例

移除全局DispatchGroup

删除类中的全局group变量:

// 移除该行
// let group = DispatchGroup()

修改viewDidLoad

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .systemBackground
    
    configureCompositionalLayout()
    getCategoriesFromCache() // 仅保留统一的数据加载入口
    setupQueryRecipesVC()
    layoutUI()
    configure()
    createDismissKeyboardTapGesture()
    retrieveUserInfo()
    cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
}

重写getCategoriesFromCache

func getCategoriesFromCache() {
    let group = DispatchGroup() // 为当前任务创建独立Group
    recipes = tags.map { (tag: $0, recipe: []) }
    
    for (index, tag) in tags.enumerated() {
        group.enter()
        PersistenceManager.retrievedCategories { [weak self] result in
            guard let self = self else {
                group.leave() // 即使self释放,也要匹配enter
                return
            }
            
            switch result {
            case .success(let cachedCategories):
                if !cachedCategories.isEmpty {
                    print("Data is coming from cache: \(cachedCategories)")
                    self.updateUI(with: cachedCategories, atIndex: index)
                    group.leave()
                } else {
                    print("Data is not available in cache, making API call...")
                    self.makeAPICallForCategories(tag: tag, atIndex: index, group: group)
                }
            case .failure(let error):
                print("Error retrieving categories from cache: \(error)")
                // 缓存读取失败,发起API请求
                self.makeAPICallForCategories(tag: tag, atIndex: index, group: group)
            }
        }
    }
    
    group.notify(queue: .main) {
        self.collectionView.reloadData()
    }
}

修正makeAPICallForCategories

func makeAPICallForCategories(tag: String, atIndex index: Int, group: DispatchGroup) {
    NetworkManager.shared.getRecipesInfo(for: .searchCategory(tag)) { [weak self] category in
        defer {
            // 无论请求结果如何,确保调用leave()
            group.leave()
        }
        
        guard let self = self else { return }
        
        switch category {
        case .success(let categories):
            for category in categories {
                PersistenceManager.updateWith(category: category, actionType: .add) { error in
                    if let error = error {
                        print("Error saving category: \(error)")
                    }
                }
            }
            self.updateUI(with: categories, atIndex: index)
        case .failure(let error):
            print("API request failed for tag \(tag): \(error)")
        }
    }
}

额外建议

  • 避免在UIViewController中使用全局DispatchGroup,尽量为每个独立的异步任务组创建局部Group,减少冲突概率。
  • 异步操作中始终使用weak self避免循环引用,同时通过defer确保资源清理或计数匹配的代码一定会执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 09:15:57