我正在尝试使用Swift和Google Sheets API为iOS创建一个库存管理应用。在开始之前,我想熟悉API并确保一切正常。为此,我想创建一个getCell函数,该函数以硬编码的行和列返回单元格的内容(根据需要以A1表示法给出)。由于Swift中Sheets API的实现似乎需要回调模型(我一般是回调和异步编程的初学者),因此我还没有弄清楚如何在查询完成后返回函数中单元格的内容
。我一直遵循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一直选择在整个程序结束时调用回调函数,这很烦人。 / p>
如果对于这个简单的任务没有那么笨拙的方法,那真是太好了。
答案 0 :(得分:0)
您的问题似乎与异步调用模式有关。当您调用service.executeQuery时,它将开始一个异步查询,但是由于它正在访问Web服务,因此该查询可能需要很长时间才能完成。 executeQuery函数立即返回,查询本身可能已被其库排队。在下一次机会发送请求时,当回复返回时,将调用displayResultWithTicket(ticket:finishedWithObject:error :)回调,但与此同时,其余的getCellQuery函数也将完成,如getCell一样。
一个更好的模式是,既然您有响应(并在通知中传递回来的值),然后在回调中触发某个notification事件,然后让该通知的侦听器获取响应并对结果做适当的事情。
或者,Sheets API中可能有一种方法可以传递closure,这很像回调,但是它的代码位于传递它的函数中,因此状态管理要容易一些。我不熟悉该API,所以不确定该API是否存在。
这是Swift中managing async code上的一篇文章,其中涵盖了一些选项。虽然有点旧,但涵盖了基础知识。