Проблемы с использованием DispatchQueue для ожидания возврата данных из Cloud Firestore в приложении iOS с использованием Swift

Я пытаюсь использовать 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)")
}

И вот вывод, который распечатывает:

  1. зоны Запрос определен
  2. Введена функция getZoneMarkers
  3. Теперь возвращено из getZoneMarkers
  4. Зацикливание завершено. Результат был [0.0]
  5. Успешно получены маркеры зон
  6. Перебор результатов маркера зоны
  7. Перебор результатов маркера зоны. Полученный маркер зоны равен 12,0.
  8. Перебор результатов маркера зоны

Спасибо за помощь!

РЕДАКТИРОВАТЬ: Если кто-то еще борется с этим, вот рабочий код, который я собрал в конце на основе полученных отзывов. Пожалуйста, не стесняйтесь критиковать, если вы видите, как это можно улучшить:

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)")   

}

person MarcE    schedule 10.02.2020    source источник
comment
DispatchQueue.main.async, вероятно, должен быть внутри закрытия zonesQuery.getDocuments()   -  person MadProgrammer    schedule 11.02.2020
comment
Вы не можете return использовать асинхронный метод. Даже если вы правильно реализовали ожидание, есть большая вероятность, что вы в конечном итоге заблокируете основную очередь. Вы должны передать закрытие завершения в свою функцию getZoneMarkers и передать, а затем вызвать это закрытие с вашими полученными маркерами. См. stackoverflow.com /вопросы/25203556/   -  person Paulw11    schedule 11.02.2020
comment
вам действительно нужно потратить время и понять, как работают асинхронные методы: firebase.googleblog.com/2018/07/   -  person Kiril S.    schedule 11.02.2020
comment
Почему вы использовали QOS: userInteractive?.   -  person Paresh. P    schedule 11.02.2020
comment
Спасибо за все предложения! Я изучил все, что вы, ребята, сказали, а также предложенные ответы ниже, и решил, что лучшим подходом для меня было отказаться от DispatchQueue и использовать обработчик завершения. Пришлось прочитать кучу разных блогов о них, так как я нашел это совершенно запутанным, но, наконец, это заработало. @Paresh.P - QOS: userinteractive просто поиграл, чтобы посмотреть, повлияет ли изменение приоритета на выполнение.   -  person MarcE    schedule 12.02.2020


Ответы (2)


Из вопроса не видно, что какие-либо DispatchQueue нужны. Firestore асинхронный, поэтому данные действительны только внутри замыканий, следующих за функцией firebase. Кроме того, вызовы пользовательского интерфейса обрабатываются в основном потоке, а сетевые — в фоновом.

Если вы хотите дождаться загрузки всех данных из 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 вы не можете «возвратиться» из замыкания, но при необходимости можете использовать обработчик завершения для передачи данных «обратно» из замыкания.

person Jay    schedule 11.02.2020
comment
Спасибо @Jay, это было полезно. Основываясь на всех мнениях, я заработал с помощью обработчика завершения - я отредактирую свой исходный вопрос, чтобы включить то, что я в итоге сделал. - person MarcE; 12.02.2020

Может быть, это может помочь вам. У меня много пользователей, он добавляется к моей модели и может проверить, когда у меня есть все данные, и продолжить мой код:

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")
}

}

person JohnDole    schedule 11.02.2020