循环发起网络请求导致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
相关产品推荐
相关产品推荐

