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

Posted

技术标签:

【中文标题】SwiftUI - 等到 Firestore getDocuments() 完成后再继续【英文标题】:SwiftUI - Wait until Firestore getDocuments() is finished before moving on 【发布时间】:2021-06-19 13:12:43 【问题描述】:

我知道有几个问题具有类似的结构,但我真的很苦恼如何让我的代码调用检索 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)
    

问题在于,progresssInProgress 变量被初始化为一个空数组,因为父级不会等到被调用函数完成获取 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 


问题是在 getDocuments() 方法完成之前返回了数组 promisesToReturn,所以每次都返回一个空数组。请有人能帮我理解如何让方法等到这个调用完成吗?谢谢

N.B Pledge 是一种自定义数据类型但没关系,它是关于了解如何等待异步功能完成。您可以将 Pledge 数据类型替换为您喜欢的任何数据类型,它仍然是相同的原则

【问题讨论】:

您不想“等待”,除非您使用的是 ios 15 的新 async await。您应该将pledgesToReturn 放在@Published 之外,以便在完成后更新并刷新View 使用完成处理程序***.com/a/67456200/14733292 @loremipsum 是我想要发布的pledgesInProgress 数组吗?这是我在调用函数中分配返回值的那个,并将在代码中进一步使用? 不会,因为您将直接引用正在填充的变量。您还没有显示所有代码,但我假设您的方法在 ViewModel 中只需引用 ViewModel 中的变量 你可以随心所欲地调用它,名字并不重要,关键是你的方法应该更新一个 SwiftUI 包装器变量,以便视图可以获取更新 【参考方案1】:

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

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

使用完成处理程序

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

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

func getPledgesInProgress(pledgePicked: Pledge, completionHandler: ([Pledges]) -> Void) 
 
  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))
    )

      // call the completion handler and pass the result array
      completionHandler(pledgesToReturn]
  


这是视图:


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

  var body: some View 
    VStack        
      // LOTS OF CODE HERE WHICH ISN'T RELEVANT
    
    .onAppear 
      getPledgesInProgress(pledgePicked: pledgePicked)  pledges in
        self.pledgesPicked = pledges
      
    
      

使用 async/await (Swift 5.5)

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

    将所有异步函数标记为async 使用await调用所有异步函数 (如果您使用视图模型)将您的视图模型标记为@MainActor 要在 SwiftUI 中调用异步代码,您可以使用 task 视图修饰符在视图出现时执行代码。或者,如果您想从按钮处理程序或其他同步上下文中调用,请将调用包装在 async await callYourAsyncFunction() 中。

要了解更多信息,请查看我的文章Getting Started with async/await in SwiftUI(视频即将推出)。我有一篇关于 async/await 和 Firestore 的文章正在编写中——一旦它上线,我会更新这个答案。

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

  let snapshot = try await db.collection("Pledges").getDocuments()
  snapshot.documents.forEach  documentSnapshot in
    let documentData = documentSnapshot.data()
    pledgesToReturn.append(findPledgeWithThisID(ID: documentData["ID"] as! Int))
  

  // async/await allows you to return the result just like in a normal function:
  return pledgesToReturn
  


这是视图:


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

  var body: some View 
    VStack        
      // LOTS OF CODE HERE WHICH ISN'T RELEVANT
    
    .task 
      self.pledgesPicked = await getPledgesInProgress(pledgePicked: pledgePicked)
    
      

一些一般性说明

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

    考虑使用 Codable 映射您的文档。请参阅 Mapping Firestore Data in Swift - The Comprehensive Guide,其中我解释了如何在 Swift 和 Firestore 之间映射最常见的数据结构。 请不要使用强制解包操作符 - 如果解包对象为 nil,您的应用程序将崩溃。在这种情况下,使用 Codable 将帮助您避免使用此运算符,但在其他情况下,您应该考虑使用 if letguard let、可选的带和不带默认值的展开。查看Optionals In Swift: The Ultimate Guide – LearnAppMaking了解更多详情。 我强烈建议使用视图模型来封装您的数据访问逻辑,尤其是在访问 Firestore 或任何其他远程后端时。查看this code 了解如何做到这一点。

【讨论】:

以上是关于SwiftUI - 等到 Firestore getDocuments() 完成后再继续的主要内容,如果未能解决你的问题,请参考以下文章

等到firestore中的数据成功更新

Swiftui + Firestore - 无法访问 Firestore 中的字段

SwiftUI + Firestore - 基于从 Firestore 返回的数组的过滤器列表

无法使用 SwiftUI 从 Firebase / Firestore 中删除文档

SwiftUI+Firestore 中的多选列表

SwiftUI:如何阅读 Firestore 中的特定文档?