如何在"GTLRSheetsQuery_SpreadsheetsValuesGet"查询完成执行后返回查询的结果(使用 Sheets API 的 Swift)?



我正在尝试使用 Swift 和 Google Sheets API 为 iOS 创建一个库存管理应用程序。在开始之前,我想熟悉 API 并确保一切正常。为此,我想创建一个函数 getCell,该函数以硬编码的行和列返回单元格的内容(根据需要以 A1 表示法给出)。由于 Sheets API 在 Swift 中的实现似乎需要一个回调模型(我是一般回调和异步编程的初学者),我还没有弄清楚如何在查询完成后在我的函数中返回单元格的内容。

我遵循了Google(https://developers.google.com/sheets/quickstart/ios?ver=swift&hl=zh-cn)提供的Sheets API iOS快速入门,在开发人员控制台中启用API并安装必要的Cocoapods(我的Podfile安装了"GoogleAPIClientForREST/Sheets"和"GoogleSignIn")。在将某些部分转换为较新的 Swift 语法后,我得到了包含的代码可以正常运行和工作。

当我在 ViewController.swift 文件中编写 getCell 函数时,出现了这个问题。从下面的代码中可以看出,我假设查询将在getCellQuery完成时完成,以便getCell能够访问由回调函数(displayResultWithTicket)更新的全局变量。但是,我放入的调试打印语句表明,回调函数仅在程序结束时执行,在getCell检查全局变量并且没有发现任何新内容之后。请注意,电子表格 ID 需要替换为真实 ID 才能运行此代码。

import GoogleAPIClientForREST
import GoogleSignIn
import UIKit
struct MyVariables {
static var currentCell: String? = nil
}
class ViewController: UIViewController, GIDSignInDelegate, GIDSignInUIDelegate {
private let scopes = [kGTLRAuthScopeSheetsSpreadsheets]
private let service = GTLRSheetsService()
@IBOutlet weak var signInButton: GIDSignInButton!
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().scopes = scopes
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
print("(error.localizedDescription)")
self.service.authorizer = nil
} else {
self.signInButton.isHidden = true
self.service.authorizer = user.authentication.fetcherAuthorizer()
//main program starts here
print("Cell: " + getCell(a1: "Inventory!A1"))
}
}
func getCell(a1: String) -> String {
print("Started getCell")
getCellQuery(a1: a1)
if MyVariables.currentCell != nil {
print("Finished getCell")
return MyVariables.currentCell!
} else {
print("Error: the global variable is nil and probably hasn't been changed")
print("Finished getCell")
return ""
}
}
func getCellQuery(a1: String) {
print("Started getCellQuery")
let spreadsheetId = "INSERT ID HERE"
// arbitrary row and column in A1 notation
let getRange = a1
let getQuery = GTLRSheetsQuery_SpreadsheetsValuesGet.query(withSpreadsheetId: spreadsheetId, range:getRange)
service.executeQuery(getQuery, delegate: self, didFinish: #selector(displayResultWithTicket(ticket:finishedWithObject:error:)))
print("Finished getCellQuery")
}
@objc func displayResultWithTicket(ticket: GTLRServiceTicket,
finishedWithObject result : GTLRSheets_ValueRange,
error : NSError?) {
print("Started callback")
if let error = error {
print("(error.localizedDescription)")
return
}
// turn 2d array into string
let rows = result.values!
for row in rows {
for item in row {
// update global variable
MyVariables.currentCell = item as? String
}
}
print("Finished callback")
}
}

登录后,我希望控制台中出现以下情况:

Started getCell
Started getCellQuery
Started callback
Finished callback
Finished getCellQuery
Finished getCell
Cell: <Whatever the cell has>

相反,我得到这个:

Started getCell
Started getCellQuery
Finished getCellQuery
Error: the cell value is nil and probably hasn't been changed
Finished getCell
Cell: 
Started callback
Finished callback

另外,我也尝试使用 while 循环来等待全局变量更新,但似乎 Swift 一直选择在整个程序结束时调用回调函数,这很烦人。

如果对于这个简单的任务有一种不那么笨拙的方法,那就太好了。

看起来您的问题与异步调用模式有关。当您调用 service.executeQuery 时,它将开始异步查询,但由于它正在访问 Web 服务,因此该查询可能需要很长时间才能完成。executeQuery 函数会立即返回,查询本身可能由其库排队。在下一次机会中,发送请求,当回复返回时,您的 displayResultWithTicket(ticket:doneWithObject:error:) 回调被调用,但与此同时,getCellQuery 函数的其余部分完成,getCell 也是如此。

更好的模式是在回调中触发一些通知事件,现在您有一个响应(并传递你在通知中返回的值),然后让该通知的侦听器获取响应并对结果执行适当的操作。

或者,工作表 API 中可能有一个方法来传递闭包,这很像回调,但它的代码位于传递它的函数中,因此状态管理更容易一些。我不熟悉该 API,所以我不确定它是否存在。

这里有一篇关于在 Swift 中管理异步代码的文章,其中涵盖了一些选项。 它有点旧,但它涵盖了基础知识。

相关内容

  • 没有找到相关文章

最新更新