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

循环发起网络请求导致iOS应用崩溃,寻求问题排查帮助

问题排查与修复方案

先帮你拆解崩溃的核心原因,主要是死锁加上DispatchGroup的错误使用,还有线程同步问题,咱们一步步理清楚:

1. 核心崩溃根源:主队列死锁

你的按钮点击事件是在主队列执行的,而sendRecipe里用了DispatchQueue.global().sync——这个方法会让主队列阻塞,等待后台队列的任务完成。但你的APIManager网络请求的回调(onSuccess/onFailure)大概率是默认回到主队列执行的,这时候主队列已经被sync卡死了,回调根本没法执行,直接导致死锁崩溃。

另外sendStep里把stepGroup.wait()放在了for循环内部,每次循环都阻塞当前线程,进一步加剧了死锁风险。

2. DispatchGroup与回调逻辑的错误使用

  • 在sendRequire和sendStep中,你在单个请求的成功回调里就调用了success(),这会导致每完成一个请求就触发一次success,但你的逻辑应该是所有请求都完成后才调用一次success。
  • sendStep的stepGroup.wait()位置错误,应该放在for循环外面,等所有请求都enter之后再等待;更推荐用notify替代wait,避免线程阻塞。
  • 变量fail在多线程环境下被修改(网络回调线程和循环线程),没有同步保护,会导致竞态条件,逻辑混乱。

修复后的代码示例

修正sendCourse(优化回调逻辑的可读性)

private func sendCourse(success: @escaping(() -> Void), errorHandling: @escaping((String) -> Void)) {
    let dataCourse:[String:String] = [
        "name":self.course.name,
        "course_type_id":String(self.course.type.id),
        "country_code":self.course.country.countryCode,
        "dishes_number":String(self.course.dishesNumber),
        "description":self.course.description
    ]
    
    APIManager.sharedInstance.put(action: .courses, data: dataCourse, 
        onSuccess: {(responseCode, data) -> Void in
            guard responseCode == 201 else {
                errorHandling("service unavailable")
                return
            }
            let jsonDecoder = JSONDecoder()
            guard let course = try? jsonDecoder.decode(Courses.self, from: data) else {
                errorHandling("service unavailable")
                return
            }
            self.course = course
            success()
        }, 
        onFailure: {(error) -> Void in
            errorHandling(error.localizedDescription)
        })
}

修正sendRequire(用notify替代wait,统一触发success)

private func sendRequire(success: @escaping(() -> Void), errorHandling: @escaping((String) -> Void)) {
    let requireGroup = DispatchGroup()
    var fail = false
    let lock = NSLock() // 加锁保护多线程访问的fail变量
    
    for require in requires {
        lock.lock()
        if fail {
            lock.unlock()
            break
        }
        lock.unlock()
        
        requireGroup.enter()
        let dataRequire:[String:String] = [
            "course_id":String(self.course.id!),
            "ingredient_id":String(require.ingredient.id),
            "quantity":String(format: "%f", require.quantity)
        ]
        
        APIManager.sharedInstance.put(action: .requires, data: dataRequire,
            onSuccess: {(responseCode, data) -> Void in
                lock.lock()
                defer { lock.unlock() } // 确保锁一定会被释放
                
                guard responseCode == 201 else {
                    fail = true
                    requireGroup.leave()
                    errorHandling("Service unavailable")
                    return
                }
                requireGroup.leave()
            }, 
            onFailure: {(error) -> Void in
                lock.lock()
                fail = true
                lock.unlock()
                requireGroup.leave()
                errorHandling(error.localizedDescription)
            })
    }
    
    // 所有请求完成后统一调用success
    requireGroup.notify(queue: .global()) {
        lock.lock()
        defer { lock.unlock() }
        if !fail {
            success()
        }
    }
}

修正sendStep(移除循环内的wait,修复success触发时机)

private func sendStep(success: @escaping(() -> Void), errorHandling: @escaping((String) -> Void)) {
    let stepGroup = DispatchGroup()
    var fail = false
    let lock = NSLock()
    
    for (index, step) in self.steps.enumerated() {
        lock.lock()
        if fail {
            lock.unlock()
            break
        }
        lock.unlock()
        
        stepGroup.enter()
        let previousStepId = index == 0 ? 0 : self.steps[index-1].id!
        let dataStep:[String:String] = [
            "course_id":String(self.course.id!),
            "description":step.description,
            "duration_hours":String(step.durationHours),
            "duration_minutes":String(step.durationMinutes),
            "duration_seconds":String(step.durationSeconds),
            "previous_step_id":String(previousStepId)
        ]
        
        APIManager.sharedInstance.put(action: .steps, data: dataStep,
            onSuccess: {(responseCode, data) -> Void in
                lock.lock()
                defer { lock.unlock() }
                
                guard responseCode == 201 else {
                    fail = true
                    stepGroup.leave()
                    errorHandling("service unavailable")
                    return
                }
                
                let jsonDecoder = JSONDecoder()
                guard let decodedStep = try? jsonDecoder.decode(Step.self, from: data) else {
                    fail = true
                    stepGroup.leave()
                    errorHandling("service unavailable")
                    return
                }
                
                self.steps[index] = decodedStep
                stepGroup.leave()
            }, 
            onFailure: {(error) -> Void in
                lock.lock()
                fail = true
                lock.unlock()
                stepGroup.leave()
                errorHandling(error.localizedDescription)
            })
    }
    
    stepGroup.notify(queue: .global()) {
        lock.lock()
        defer { lock.unlock() }
        if !fail {
            success()
        }
    }
}

修正sendRecipe(用链式异步调用替代sync,彻底避免死锁)

func sendRecipe(errorHandling: @escaping((String) -> Void)) {
    // 链式调用,保证前一个任务完成后再执行下一个
    sendCourse(success: { [weak self] in
        self?.sendRequire(success: { [weak self] in
            self?.sendStep(success: {}, errorHandling: errorHandling)
        }, errorHandling: errorHandling)
    }, errorHandling: errorHandling)
}

额外注意点

  • 用[weak self]避免循环引用,防止内存泄漏。
  • 用NSLock保护多线程访问的变量,避免竞态条件导致的逻辑错误。
  • 尽量避免使用wait(),改用notify()处理异步任务的完成通知,减少线程阻塞风险。
  • 网络请求回调如果不在主队列,更新UI时一定要切回主队列(你的错误提示已经做了这一点,保持即可)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:09:17