Как заставить цикл for-in ждать завершения функции выборки данных

Я пытаюсь получить кучу данных с помощью функции цикла for in, но она не возвращает данные в правильном порядке. Похоже, что для получения некоторых данных требуется больше времени, поэтому они смешаны в массиве, где мне нужно, чтобы все данные были в правильном порядке. Итак, я использовал DispatchGroup. Однако это не работает. Не могли бы вы сообщить мне, что я здесь делаю не так? Провел последние 10 часов в поисках решения... ниже мой код.

@IBAction func parseXMLTapped(_ sender: Any) {

    let codeArray = codes[0]
    for code in codeArray {
                    
              self.fetchData(code)
                    
     }
                
      dispatchGroup.notify(queue: .main) {
          print(self.dataToAddArray)
          print("Complete.")
     }

}

private func fetchData(_ code: String) {
        dispatchGroup.enter()
        print("count: \(count)")

        let dataParser = DataParser()
        dataParser.parseData(url: url) { (dataItems) in
            self.dataItems = dataItems
            
            print("Index #\(self.count): \(self.dataItems)")
            self.dataToAddArray.append(self.dataItems)

        }
        self.dispatchGroup.leave()
        
        dispatchGroup.enter()
        self.count += 1
        dispatchGroup.leave()
        
    }

3 ответа

Решение

В этом случае с помощью DispathSemaphore:

    let semaphore = DispatchSemaphore(value: 0)
        DispatchQueue.global().async {
            for code in codeArray {
                self.fetchData(code)
                semaphore.wait()
            }

        }
    private func fetchData(_ code: String) {
        print("count: \(count)")

        let dataParser = DataParser()
        dataParser.parseData(url: url) { (dataItems) in
            self.dataItems = dataItems

            print("Index #\(self.count): \(self.dataItems)")
            self.dataToAddArray.append(self.dataItems)
            semaphore.signal()
        }
    }

Проблема с асинхронными функциями заключается в том, что вы никогда не узнаете, в каком порядке возвращаются блоки. Если вам нужно сохранить порядок, используйте такие индексы:

let dispatchGroup = DispatchGroup()
var dataToAddArray = [String](repeating: "", count: codeArray.count)
for (index, code) in codeArray.enumerated() {
    dispatchGroup.enter()
    DataParser().parseData(url: url) { dataItems in
        dataToAddArray[index] = dataItems
        dispatchGroup.leave()
    }
}
dispatchGroup.notify(queue: .main) {
    print("Complete"
}

Также в вашем примере вы звоните dispatchGroup.leave()еще до того, как асинхронный блок завершится. Это также приведет к неверным результатам.

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

Сказав это, я бы, вероятно, использовал словарь:

      let group = DispatchGroup()
var results: [String: [DataItem]]                        // you didn't say what `dataItems` was, so I'll assume it's an array of `DataItem` objects; but this detail isn't material to the broader question

for code in codeArray {
    group.enter()
    DataParser().parseData(url: url) { dataItems in
        results[code] = dataItems                        // if parseData doesn't already uses the main queue for its completion handler, then dispatch these two lines to the main queue
        group.leave()
    }
}

group.notify(queue: .main) {
    let sortedResults = codes.compactMap { results[$0] } // this very efficiently gets the results in the right order
    // do something with sortedResults
}

Теперь я мог бы посоветовать ограничить степень параллелизма (например, возможно, вы захотите ограничить это количеством процессоров или некоторым разумным фиксированным числом (например, 4 или 6). Это отдельный вопрос. Но я бы посоветовал не жертвовать параллелизмом только ради получите результаты в правильном порядке.

Другие вопросы по тегам