如何使用Swift的DispatchQueue和completionHandler组织网络登录流程
问题根源
首先明确两个核心错误点:
httpRequest本身是异步网络请求方法,你向串行队列/OperationQueue提交的任务只会执行到方法调用就结束,队列不会等待内部网络请求完成,自然无法保证执行顺序。- 登录成功后存储token的逻辑用了
DispatchQueue.main.async异步提交到主队列,就算你在请求的success回调里触发下载,存储token的任务会排在当前回调执行完毕后才运行,自然会出现下载触发时token还未赋值的情况。
修复方案
第一步:修正token存储逻辑
把登录成功后的token存储逻辑从异步提交改成同步执行,或者确保存储完成后再触发成功回调。如果没有特殊UI更新需求,直接在请求的回调队列里执行存储即可,不需要额外派发主队列:
// 原内部逻辑修改,不需要DispatchQueue.main.async包裹 let jsonObject = try? decodeParameter(data: data) self.httpToken = jsonObject?.httpToken self.sessionName = parameter.sessionName! // 确认存储完成后,再执行外部传入的completionHandler completionHandler(.success(jsonObject))
如果确实需要在主队列存储token,那就把completionHandler的调用也放到同一个主队列异步块里,保证存储完成后才回调:
DispatchQueue.main.async { let jsonObject = try? decodeParameter(data: data) self.httpToken = jsonObject?.httpToken self.sessionName = parameter.sessionName! // 存储完成后再触发回调 completionHandler(.success(jsonObject)) }
第二步:利用completionHandler保证任务顺序
直接在登录请求的success回调里触发下载任务即可,不需要额外创建串行队列:
httpRequest(requestType: .login, parameter: 你的登录参数) { result in switch result { case .success: // 此时token已经存储完成,启动下载 httpRequest(requestType: .download, parameter: 下载参数) { downloadResult in // 后续下载完成的逻辑 } case .failure(let error): // 处理登录失败逻辑 print(error) } }
第三步:多依赖任务的扩展方案
如果后续有大量有依赖关系的异步任务,可以用以下两种方案管理:
- 使用
async/await(Swift 5.5+支持)
把httpRequest包装成async方法,依赖关系直接用线性代码书写即可,逻辑最清晰:
// 先包装httpRequest为async方法 func httpRequestAsync(requestType: HTTPRequestType, parameter: Parameter = Parameter()) async throws -> Parameter? { try await withCheckedThrowingContinuation { continuation in httpRequest(requestType: requestType, parameter: parameter) { result in switch result { case .success(let param): continuation.resume(returning: param) case .failure(let error): continuation.resume(throwing: error) } } } } // 调用时顺序执行 Task { do { // 登录完成后才会执行下一步 _ = try await httpRequestAsync(requestType: .login, parameter: 登录参数) // 下载1 _ = try await httpRequestAsync(requestType: .download1) // 下载1完成后执行下载2 _ = try await httpRequestAsync(requestType: .download2) } catch { // 统一处理错误 print(error) } }
- 使用
Operation子类封装异步任务
如果需要兼容旧版本Swift,自定义Operation子类,手动管理isExecuting、isFinished状态,只有当网络请求完成后才修改任务状态为finished,这样串行OperationQueue或者任务依赖才能生效。
临时兜底方案
如果暂时不想修改内部存储逻辑,可以在下载请求前加判空,间隔重试直到token存在,不推荐作为常规方案,仅可用于临时调试:
func startDownloadAfterTokenReady() { guard let _ = self.httpToken else { DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) { [weak self] in self?.startDownloadAfterTokenReady() } return } httpRequest(requestType: .download) }
内容的提问来源于stack exchange,提问作者JacquesNorris
相关产品推荐
相关产品推荐

