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

SwiftUI中异步回调调用Google Sheets API返回结果异常排查

解决方案

核心问题分析

你的nameSearch是同步函数,但内部的Google令牌刷新、Alamofire请求都是异步操作。函数执行到return data时,异步代码还未完成,data始终是初始的nil,这就是为什么一直输出"Error"。


方案一:使用Swift async/await(推荐)

这是Swift现代异步编程的标准方式,代码更简洁易读。

步骤1:将nameSearch改为异步函数

先把Google的回调式令牌刷新方法包装成异步版本,再用Alamofire的async/await接口发起请求:

import Alamofire

static func nameSearch<S: StringProtocol>(name: S, advisory: S = "", user: GIDGoogleUser?) async throws -> [String] {
    guard let user = user else {
        throw NSError(domain: "SheetsAPI", code: 1, userInfo: [NSLocalizedDescriptionKey: "用户未登录"])
    }
    
    let nameStr = String(name)
    let advisoryStr = String(advisory)
    let writeRange = "'App Control'!A2:C2"
    let readRange = "'App Control'!A4:V4"
    
    // 包装Google令牌刷新为异步操作
    let authentication = try await withCheckedThrowingContinuation { continuation in
        user.authentication.doWithFreshTokens { auth, error in
            if let error = error {
                continuation.resume(throwing: error)
                return
            }
            guard let auth = auth else {
                continuation.resume(throwing: NSError(domain: "SheetsAPI", code: 2, userInfo: [NSLocalizedDescriptionKey: "获取令牌失败"]))
                return
            }
            continuation.resume(returning: auth)
        }
    }
    
    let token = authentication.accessToken
    let headers: HTTPHeaders = ["Authorization": "Bearer \(token)"]
    
    // 第一个PUT请求(写入数据)
    let putParameters: [String: Any] = [
        "range": writeRange,
        "values": [[nameStr, nil, advisoryStr]]
    ]
    
    _ = try await AF.request(
        "你的PUT请求URL",
        method: .put,
        parameters: putParameters,
        encoding: JSONEncoding.default,
        headers: headers
    ).serializingString().value
    
    // 第二个GET请求(读取数据)
    let responseData = try await AF.request(
        "你的GET请求URL",
        headers: headers
    ).serializingDecodable(NameResponseModel.self).value
    
    guard let result = responseData.values.first else {
        throw NSError(domain: "SheetsAPI", code: 3, userInfo: [NSLocalizedDescriptionKey: "未获取到有效数据"])
    }
    
    return result
}

步骤2:在SwiftUI按钮中调用异步函数

SwiftUI按钮闭包需要标记为async:

Button("Search") {
    Task {
        guard !fullName.isEmpty else { return }
        do {
            let result = try await SheetsAPI.nameSearch(name: fullName, user: vm.getUser())
            print(result)
        } catch {
            print("Error: \(error.localizedDescription)")
        }
    }
}

方案二:使用回调函数(兼容旧代码习惯)

如果你暂时无法理解async/await,可以用回调方式修改函数:

修改nameSearch为带回调的函数

static func nameSearch<S: StringProtocol>(name: S, advisory: S = "", user: GIDGoogleUser?, completion: @escaping ([String]?, Error?) -> Void) {
    guard let user = user else {
        completion(nil, NSError(domain: "SheetsAPI", code: 1, userInfo: [NSLocalizedDescriptionKey: "用户未登录"]))
        return
    }
    
    let nameStr = String(name)
    let advisoryStr = String(advisory)
    let writeRange = "'App Control'!A2:C2"
    let readRange = "'App Control'!A4:V4"
    
    user.authentication.doWithFreshTokens { authentication, error in
        if let error = error {
            completion(nil, error)
            return
        }
        guard let authentication = authentication else {
            completion(nil, NSError(domain: "SheetsAPI", code: 2, userInfo: [NSLocalizedDescriptionKey: "获取令牌失败"]))
            return
        }
        
        let token = authentication.accessToken
        let headers: HTTPHeaders = ["Authorization": "Bearer \(token)"]
        
        let parameters: [String: Any] = [
            "range": writeRange,
            "values": [[nameStr, nil, advisoryStr]]
        ]
        
        AF.request("你的PUT请求URL", method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString { response in
            switch response.result {
            case .success:
                AF.request("你的GET请求URL", headers: headers).responseDecodable(of: NameResponseModel.self) { response2 in
                    switch response2.result {
                    case .success(let data):
                        completion(data.values.first, nil)
                    case .failure(let error):
                        completion(nil, error)
                    }
                }
            case .failure(let error):
                completion(nil, error)
            }
        }
    }
}

在按钮中调用带回调的函数

Button("Search") {
    guard !fullName.isEmpty else { return }
    SheetsAPI.nameSearch(name: fullName, user: vm.getUser()) { result, error in
        if let result = result {
            print(result)
        } else {
            print("Error: \(error?.localizedDescription ?? "Unknown error")")
        }
    }
}

额外注意事项

  1. 替换代码中的"你的PUT请求URL"和"你的GET请求URL"为实际的Google Sheets API地址。
  2. 避免使用user!强制解包,改用guard let处理,防止未登录时崩溃。
  3. Alamofire的serializingDecodable是async/await版本接口,需确保你的Alamofire版本为5.4+。

内容的提问来源于stack exchange,提问作者Cade Missner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 20:54:23