循环中闭包回调致rawData被覆盖的递归函数解决方案咨询
问题分析与解决方案
问题描述
我正在使用一个递归函数,该函数会调用子函数,子函数在特定条件下会回调该递归函数。这些函数借助闭包返回数据,因为它们需要执行包含API调用的处理逻辑。由于通过闭包实现递归数据返回,函数以rawData作为参数,但在for循环中调用时,该参数会被覆盖。因API调用无法避免使用闭包,现寻求可实现类似结构的解决方案(可使用或不使用Dispatch Group)。
示例代码
func recursive(rawDataMap: [String: [RawData]], completion: @escaping ([String: [ProcessedData]]) -> Void) { var processedDataMap = [String: [ProcessedData]]() let parentDispatchGroup = DispatchGroup() // 示例中未处理key的存储,实际代码会保留key for (key, data) in rawDataMap { parentDispatchGroup.enter() if data.count > 1 { let childDispatchGroup = DispatchGroup() var processedDataArray = [ProcessedData]() for item in data { childDispatchGroup.enter() children(rawData: item){ processedData in processedDataArray.append(processedData) childDispatchGroup.leave() } } childDispatchGroup.notify(queue: .main){ processedDataMap[key] = processedDataArray parentDispatchGroup.leave() } } } parentDispatchGroup.notify(queue: .main) { completion(processedDataMap) } } func children(rawData: RawData, completion: @escaping (ProcessedData) -> Void) { // 判断是否需要递归调用 if rawData.nested { recursive(rawDataMap: rawData.rawDataMap){ processedDataMap in completion(ProcessedData(nested: true, processedDataMap: processedDataMap)) } } else { processing(rawData: rawData){ processedData in completion(processedData) } } } func processing(rawData: RawData, completion: @escaping (ProcessedData) -> Void) { // 从rawData生成API参数 let params = getParamsFromData(rawData: rawData) networkClient.api(params: params){ processedData in completion(processedData) } }
核心问题
循环中闭包捕获变量时,由于闭包是异步执行的,循环迭代会先于闭包完成,导致闭包捕获到的是循环变量的最终值而非当前迭代的值;同时processedDataArray的append操作存在线程安全风险(API回调可能在后台线程执行)。
解决方案
方案1:修复闭包捕获与线程安全问题(基于Dispatch Group)
1.1 显式捕获循环变量副本
在for循环内部创建局部变量保存当前迭代的item和key,避免闭包捕获引用类型的变量导致值被覆盖:
for (key, data) in rawDataMap { parentDispatchGroup.enter() let currentKey = key // 捕获当前key的副本 if data.count > 1 { let childDispatchGroup = DispatchGroup() var processedDataArray = [ProcessedData]() let serialQueue = DispatchQueue(label: "com.processedData.queue") // 串行队列保证线程安全 for item in data { let currentItem = item // 捕获当前item的副本 childDispatchGroup.enter() children(rawData: currentItem){ processedData in serialQueue.async { // 在串行队列中修改数组,避免线程冲突 processedDataArray.append(processedData) childDispatchGroup.leave() } } } childDispatchGroup.notify(queue: .main){ processedDataMap[currentKey] = processedDataArray parentDispatchGroup.leave() } } }
1.2 线程安全处理
使用串行队列包裹processedDataArray的append操作,避免多线程同时修改数组导致的数据竞争。
方案2:使用Async/Await替代闭包(Swift 5.5+)
Swift 5.5引入的Async/Await可以彻底避免闭包捕获问题,代码结构更简洁易读:
func recursive(rawDataMap: [String: [RawData]]) async -> [String: [ProcessedData]] { var processedDataMap = [String: [ProcessedData]]() for (key, data) in rawDataMap { if data.count > 1 { var processedDataArray = [ProcessedData]() for item in data { let processedData = await children(rawData: item) processedDataArray.append(processedData) } processedDataMap[key] = processedDataArray } } return processedDataMap } func children(rawData: RawData) async -> ProcessedData { if rawData.nested { let processedDataMap = await recursive(rawDataMap: rawData.rawDataMap) return ProcessedData(nested: true, processedDataMap: processedDataMap) } else { return await processing(rawData: rawData) } } func processing(rawData: RawData) async -> ProcessedData { let params = getParamsFromData(rawData: rawData) return await networkClient.api(params: params) } // 调用示例 Task { let processedResult = await recursive(rawDataMap: yourRawDataMap) // 在主线程更新UI或处理结果 DispatchQueue.main.async { // 处理processedResult } }
优势
- 无需手动管理Dispatch Group和闭包捕获,代码逻辑更清晰
- 自动处理异步流程的等待,避免回调地狱
- 天然保证变量捕获的正确性,不存在循环变量覆盖问题
内容的提问来源于stack exchange,提问作者Vikash Kumar Chaubey
相关产品推荐
相关产品推荐

