在使用 Swift 的 iOS 应用程序中使用 DispatchQueue 等待数据从 Cloud Firestore 返回时遇到问题

Posted

技术标签:

【中文标题】在使用 Swift 的 iOS 应用程序中使用 DispatchQueue 等待数据从 Cloud Firestore 返回时遇到问题【英文标题】:Having trouble using DispatchQueue to wait for data to return from Cloud Firestore in iOS app using Swift 【发布时间】:2020-02-10 22:31:20 【问题描述】:

我正在尝试使用 DispatchQueue 让我的代码等待查询从 Cloud Firestore 检索到我需要的结果,然后再继续执行,但只是无法让它工作。在下面的代码中,我试图让它等到数据被检索并存储在 zoneMarkerArray 中,然后打印出结果。

我已经按照我希望它发生的顺序对它打印的每一行进行了编号,并且正如您在输出中看到的那样,它不会在继续之前等待 Firestore 结果。

这是我的代码:

let zones = self.db.collection("zones")

let zonesQuery = zones.whereField("start", isGreaterThan: lowerLimit).whereField("start", isLessThan: upperLimit)

print("1. zones Query has been defined")

//pass zonesQuery query to getZoneMarkers function to retrieve the zone markers from Firestore      
getZoneMarkers(zonesQuery)

print("6. Now returned from getZoneMarkers")


func getZoneMarkers(_ zonesQuery: Query) -> ([Double]) 
    print("2. Entered getZoneMarkers function")
    DispatchQueue.global(qos: .userInteractive).async          

        zonesQuery.getDocuments()  (snapshot, error) in

            if let error = error 
            print("Error getting zone markers: \(error)")
             else 

                print("3. Successfully Retrieved the zone markers")
            var result: Double = 0.0

                for document in snapshot!.documents 

                    print("Retrieved zone marker is \(document["start"]!)")
                    self.zoneMarkerArray.append(document["start"]! as! Double)
                    print("4. Looping over zone marker results")

                
            
        

        DispatchQueue.main.async  
      //I want this the printCompleted function to print the result AFTER the results have been retrieved  
            self.printCompleted()

        
       

    return self.zoneMarkerArray




func printCompleted() 
    print("5. Looping now completed. Result was \(zoneMarkerArray)")

这是打印出来的输出:

    区域查询已定义 进入 getZoneMarkers 函数 现在从 getZoneMarkers 返回 循环现已完成。结果为 [0.0] 成功检索区域标记 循环遍历区域标记结果 循环遍历区域标记结果 检索到的区域标记为 12.0 循环遍历区域标记结果

感谢您的帮助!

编辑:如果其他人也为此苦苦挣扎,这是我最后根据收到的反馈汇总的工作代码。如果您看到如何进一步改进,请随时批评:

let zones = self.db.collection("zones")

let zonesQuery = zones.whereField("start", isGreaterThan: lowerLimit).whereField("start", isLessThan: upperLimit)


print("1. zones Query has been defined")

//pass zonesQuery query to getZoneMarkers function to retrieve the zone markers from Firestore      
getZoneMarkers(zonesQuery)

func getZoneMarkers(_ zonesQuery: (Query)) 
    print("2. Entered getZoneMarkers function")
  zoneMarkerArray.removeAll()

    zonesQuery.getDocuments(completion:  (snapshot, error) in
        if let error = error 
            print("Error getting zone markers: \(error)")
            return
        

        guard let docs = snapshot?.documents else  return 

        print("3. Successfully Retrieved the zone markers")


        for document in docs 

            self.zoneMarkerArray.append(document["start"]! as! Double)
            print("4. Looping over zone marker results")

        

        self.completion(zoneMarkerArray: self.zoneMarkerArray)

    )



func completion(zoneMarkerArray: [Double]) 
    print("5. Looping now completed. Result was \(zoneMarkerArray)")   


【问题讨论】:

DispatchQueue.main.async 应该在 zonesQuery.getDocuments() 闭包内 你不能通过异步方法return。即使您确实正确地实现了等待,也很有可能最终会阻塞主队列。您应该将完成闭包传递给您的 getZoneMarkers 函数,然后使用检索到的标记传递然后调用该闭包。见***.com/questions/25203556/… 您真的需要花时间了解异步方法的工作原理:firebase.googleblog.com/2018/07/… 你为什么使用 QOS : userInteractive?. 感谢所有建议!我查看了你们所说的所有内容以及下面提出的答案,并决定对我来说最好的方法是放弃 DispatchQueue 并使用完成处理程序。不得不阅读一堆关于它们的不同博客,因为我发现它完全令人困惑,但终于让它工作了。 @Paresh.P - QOS:userinteractive 只是我在玩,看看更改优先级是否对执行有任何影响。 【参考方案1】:

从问题来看,似乎不需要任何 DispatchQueue。 Firestore 异步,因此数据仅在 firebase 函数之后的闭包内有效。此外,UI 调用在主线程上处理,而网络在后台线程上处理。

如果您想等待从 Firestore 加载所有数据,这将在 Firestore 调用之后的闭包内完成。例如,假设我们有一个区域集合,其中包含存储开始和停止指示符的文档

zones
   zone_0
      start: 1
      stop:  3
   zone_1
      start: 7
      stop:  9

对于这个例子,我们将把每个区域的开始和结束存储在一个元组类数组中

var tupleArray = [(Int, Int)]()

以及要在区域中读取的代码,填充 tupleArray,然后执行“下一步” - 在这种情况下打印它们。

func readZoneMarkers() 
    let zonesQuery = self.db.collection("zones")
    zonesQuery.getDocuments(completion:  documentSnapshot, error in
        if let err = error 
            print(err.localizedDescription)
            return
        

        guard let docs = documentSnapshot?.documents else  return 

        for doc in docs 
            let start = doc.get("start") as? Int ?? 0
            let end = doc.get("end") as? Int ?? 0
            let t = (start, end)
            self.tupleArray.append(t)
        

        //reload your tableView or collectionView here,
        //    or proceed to whatever the next step is
        self.tupleArray.forEach  print( $0.0, $0.1) 
    )

和输出

1 3
7 9

由于 Firebase 的异步特性,您无法从闭包中“返回”,但如果需要,您可以利用完成处理程序从闭包中“返回”数据。

【讨论】:

感谢@Jay,这很有帮助。根据每个人的输入,我使用完成处理程序让它工作 - 我将编辑我的原始问题以包括我最终做了什么。【参考方案2】:

也许这可以帮助你。我有很多用户,它附加到我的模型中,并且可以检查我何时拥有所有数据并继续使用我的代码:

func allUser (completion: @escaping ([UserModel]) -> Void) 

let dispatchGroup = DispatchGroup()
var model = [UserModel]()

let db = Firestore.firestore()
let docRef = db.collection("users")
dispatchGroup.enter()
docRef.getDocuments  (querySnapshot, err) in

    for document in querySnapshot!.documents 
        print("disp enter")
        let dic = document.data()
        model.append(UserModel(dictionary: dic))
    
     dispatchGroup.leave()

dispatchGroup.notify(queue: .main) 
    completion(model)
    print("completion")

【讨论】:

以上是关于在使用 Swift 的 iOS 应用程序中使用 DispatchQueue 等待数据从 Cloud Firestore 返回时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在iOS swift的tableview中使用json响应中的键和值?

如何在 Swift 2 的 iOS 应用程序中使用音频?

在 iOS (Swift) 中使用后台位置更新的最佳方式 [关闭]

为啥应用存档在 iOS、swift、xcode 9.2 中使用自定义框架失败

使用swift语言进行IOS应用开发

你可以在 iOS 应用程序中使用 Package.swift 而不是使用 .xcodeproj 吗?