SwiftUI - 等待 Firestore getDocuments() 完成后再继续

AAH*_*AAH 4 asynchronous swift google-cloud-firestore swiftui

我知道有几个具有类似结构的问题,但我真的很难让我的代码调用检索 Firestore 数据的方法,等到异步函数完成后再返回。

目前我有调用 的父函数getPledgesInProgress,该方法从 firebase 检索数据。初始化父级时会调用此方法。

    @State var pledgesInProgress = [Pledge]()
    var pledgePicked: Pledge?

    var body: some View {
        VStack{
       
           //LOTS OF CODE HERE WHICH ISN'T RELEVANT
    }
    
    func initVars(){
        pledgesInProgress = getPledgesInProgress(pledgePicked: pledgePicked ?? emptyPledge)
    }
}
Run Code Online (Sandbox Code Playgroud)

问题在于,pledgesInProgress 变量使用空数组进行初始化,因为父级不会等到被调用函数完成获取 Firestore 文档后再继续。

func getPledgesInProgress(pledgePicked: Pledge)-> [Pledge]{
 
    let db = Firestore.firestore()
    var pledgesToReturn = [Pledge]() //INITIALISED AS EMPTY

  
   db.collection("Pledges")
      .getDocuments { (snapshot, error) in
         guard let snapshot = snapshot, error == nil else {
          //handle error
          return
        }
  
        snapshot.documents.forEach({ (documentSnapshot) in

          let documentData = documentSnapshot.data()
                pledgesToReturn.append(findPledgeWithThisID(ID: documentData["ID"] as! Int))
        })
      
      }

//PROBLEM!!!! returned before getDocuments() completed
return pledgedToReturn 
}

Run Code Online (Sandbox Code Playgroud)

问题是数组 pledgesToReturn 在 getDocuments() 方法完成之前返回,因此每次都以空数组形式返回。请有人帮助我了解如何让方法等待此调用完成?谢谢

NB Pledge 是自定义数据类型,但这并不重要,重要的是了解如何等待异步函数完成。你可以用任何你喜欢的数据类型替换 Pledge 数据类型,它仍然是相同的原理

Pet*_*ese 13

火霸在这里。首先,请知道大多数(!)Firebase API 都是异步的,并且要求您使用完成处理程序来接收结果。随着 async/await 的普遍可用性,这将变得更容易,这将使您能够编写直线代码。我不久前录制了一个有关此内容的视频 -请观看该视频以了解如何将 Firebase 与 Swift 5.5 结合使用。

\n

有两种方法可以解决此问题:使用完成处理程序或使用 async/await。我将在下面描述这两者,但请注意 async/await 仅在 Swift 5.5 中可用并且需要 iOS 15,因此如果您正在开发一个应用程序并希望将其交付给使用该应用程序的用户,您可能需要选择完成处理程序iOS 14.x 及更低版本。

\n

使用完成处理程序

\n

这是当前处理 Firebase API 结果的方式。

\n

这是代码的更新版本,使用完成处理程序:

\n
func getPledgesInProgress(pledgePicked: Pledge, completionHandler: ([Pledges]) -> Void) {\n \n  let db = Firestore.firestore()\n  var pledgesToReturn = [Pledge]() //INITIALISED AS EMPTY\n\n  \n  db.collection("Pledges")\n    .getDocuments { (snapshot, error) in\n    guard let snapshot = snapshot, error == nil else {\n      //handle error\n      return\n    }\n  \n    snapshot.documents.forEach({ (documentSnapshot) in\n      let documentData = documentSnapshot.data()\n      pledgesToReturn.append(findPledgeWithThisID(ID: documentData["ID"] as! Int))\n    })\n\n      // call the completion handler and pass the result array\n      completionHandler(pledgesToReturn]\n  }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

这是视图:

\n
\nstruct PledgesInProgress: View {\n  @State var pledgesInProgress = [Pledge]()\n  var pledgePicked: Pledge?\n\n  var body: some View {\n    VStack {       \n      // LOTS OF CODE HERE WHICH ISN\'T RELEVANT\n    }\n    .onAppear {\n      getPledgesInProgress(pledgePicked: pledgePicked) { pledges in\n        self.pledgesPicked = pledges\n      }\n    }\n  }    \n}\n
Run Code Online (Sandbox Code Playgroud)\n

使用异步/等待(Swift 5.5)

\n

您的原始代码非常接近异步/等待实现的代码。要记住的主要事项是:

\n
    \n
  1. 将所有异步函数标记为async
  2. \n
  3. 使用调用所有异步函数await
  4. \n
  5. (如果您使用视图模型)将您的视图模型标记为@MainActor
  6. \n
  7. 要从 SwiftUI 中调用异步代码,您可以使用task视图修饰符在视图出现时执行代码。或者,如果您想从按钮处理程序或另一个同步上下文进行调用,请将调用包装在async { await callYourAsyncFunction() }.
  8. \n
\n

要了解更多相关信息,请查看我的文章《SwiftUI 中的 async/await 入门》(视频即将推出)。我有一篇关于 async/await 和 Firestore 的文章正在酝酿中 - 一旦它上线,我将更新这个答案。

\n
func getPledgesInProgress(pledgePicked: Pledge) async -> [Pledges] {\n \n  let db = Firestore.firestore()\n  var pledgesToReturn = [Pledge]() //INITIALISED AS EMPTY\n\n  let snapshot = try await db.collection("Pledges").getDocuments()\n  snapshot.documents.forEach { documentSnapshot in\n    let documentData = documentSnapshot.data()\n    pledgesToReturn.append(findPledgeWithThisID(ID: documentData["ID"] as! Int))\n  }\n\n  // async/await allows you to return the result just like in a normal function:\n  return pledgesToReturn\n  }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

这是视图:

\n
func getPledgesInProgress(pledgePicked: Pledge) async -> [Pledges] {\n \n  let db = Firestore.firestore()\n  var pledgesToReturn = [Pledge]() //INITIALISED AS EMPTY\n\n  let snapshot = try await db.collection("Pledges").getDocuments()\n  snapshot.documents.forEach { documentSnapshot in\n    let documentData = documentSnapshot.data()\n    pledgesToReturn.append(findPledgeWithThisID(ID: documentData["ID"] as! Int))\n  }\n\n  // async/await allows you to return the result just like in a normal function:\n  return pledgesToReturn\n  }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

一些一般性评论

\n

有几件事可以使您的代码更具弹性:

\n
    \n
  1. 考虑使用 Codable 来映射您的文档。请参阅在 Swift 中映射 Firestore 数据 - 综合指南,其中我解释了如何在 Swift 和 Firestore 之间映射最常见的数据结构。
  2. \n
  3. 请不要使用强制解包运算符 - 如果解包的对象为零,您的应用程序将崩溃。在这种情况下,使用 Codable 将帮助您避免使用此运算符,但在其他情况下,您应该考虑使用if letguard let、带或不带默认值的可选展开。查看Swift 中的选项:终极指南 \xe2\x80\x93 LearnAppMaking了解更多详细信息。
  4. \n
  5. 我强烈建议使用视图模型来封装数据访问逻辑,尤其是在访问 Firestore 或任何其他远程后端时。查看此代码以了解如何完成此操作。
  6. \n
\n