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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 00:45:03