Swift开发:使用DispatchQueue实现网络请求时getCoursesList返回空数组的问题
问题根因
你遇到的返回空数组问题,本质是同步返回逻辑和异步网络请求的执行时序错位,和DispatchQueue的使用没有直接关联:
URLSession发起的网络请求是完全异步执行的:调用request方法后,代码不会阻塞等待网络响应,会立刻往下执行,走到getCoursesList最后的return coursesList语句- 此时初始化的空数组
coursesList还没有被网络回调的赋值逻辑接触到,返回的自然是空数组 - 你能在回调里打印到正确数据,是因为网络响应触发回调的时机(通常是几百毫秒到几秒之后),远晚于方法return执行的时机,两个逻辑的执行顺序完全倒置
- 你在
NetworkRequest里用DispatchQueue.main.async把回调切到主线程的操作本身没有问题,这是为了保证回调里可以直接更新UI,但这个操作解决不了时序错位的核心问题
修复方案
网络请求天生是异步流程,绝对不能用同步返回值的方式获取结果,必须改成和NetworkRequest一致的闭包回调形式传递结果:
1. 改造getCoursesList方法
把方法的返回值改成完成回调闭包,等网络请求完成、数据解析结束后,再通过闭包把结果抛出去:
func getCoursesList(completion: @escaping (Result<[Course], Error>) -> Void) { let networkRequest = NetworkRequest() // 替换强制解包的token逻辑,避免token不存在时崩溃 guard let token = UserDefaults.standard.string(forKey: "Token") else { completion(.failure(NetworkError.tokenNotFound)) return } networkRequest.request( endpoint: "/webservice/rest/server.php", parameters: [ "wstoken": token, "wsfunction": "core_course_get_courses", "moodlewsrestformat": "json" ] ) { result in switch result { case .success(let data): do { // 成功拿到数据后优先解析为课程数组 let courses = try JSONDecoder().decode([Course].self, from: data) completion(.success(courses)) } catch { // 解析失败时,尝试识别接口返回的业务错误结构 if let serverError = try? JSONDecoder().decode(ErrorResponse.self, from: data) { print("接口业务错误:\(serverError)") completion(.failure(NetworkError.serverBusinessError(serverError.message))) } else { print("数据解析失败:\(error.localizedDescription)") completion(.failure(error)) } } case .failure(let error): print("网络请求失败:\(error.localizedDescription)") completion(.failure(error)) } } }
2. 正确调用改造后的方法
因为是异步流程,调用时不能用同步赋值的方式接收结果,必须在闭包内处理返回的课程列表:
// 调用示例 getCoursesList { result in switch result { case .success(let courses): // 这里拿到的就是解析完成的完整课程数组,可以直接用来刷新界面、赋值给页面变量 print("加载完成的课程列表:\(courses)") case .failure(let error): // 统一处理加载失败的提示、错误上报 print("加载课程列表失败:\(error.localizedDescription)") } }
附带的小优化建议
- 原来的
NetworkRequest实现里,把响应状态校验、错误判断、数据处理的逻辑全部放在DispatchQueue.main.async块里,虽然不影响功能,但这些校验逻辑完全可以在URLSession的后台回调线程里执行,不需要强制切到主线程,减少不必要的线程切换开销 - 现在的POST请求没有设置
Content-Type头,Moodle接口可能无法正确识别你放在httpBody里的参数,可以补充设置request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type"),避免接口返回参数错误的问题
内容的提问来源于stack exchange,提问作者Lucas Scherer
相关产品推荐
相关产品推荐

