几个问题:
您有执行路径,如果发生错误,您不会调用leave
. 确保每个执行路径,包括每个“提前退出”,都enter
使用leave
.
您定义dataArray
为可选项,但从不初始化它。因此它是nil
。因此dataArray?.append(weatherData)
永远不会附加值。
因此,也许:
func fetchWeatherForCities (completionHandler: @escaping ([YandexWeatherData]) -> Void) {
var dataArray: [YandexWeatherData] = []
let group = DispatchGroup()
for city in cities {
group.enter()
var urlString = self.urlString
self.locationManager.getCoordinate(forCity: city) { (coordinate) in
urlString += self.latitudeField + coordinate.latitude
urlString += self.longitudeField + coordinate.longitude
guard let url = URL(string: urlString) else {
group.leave() // make sure to `leave` in early exit
return
}
var request = URLRequest(url: url)
request.addValue(self.apiKey, forHTTPHeaderField: self.apiField)
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
guard
let data = data,
error == nil,
let weatherData = self.parseJSON(withData: data)
else {
group.leave() // make sure to `leave` in early exit
print(error ?? "unknown error")
return
}
print(weatherData.fact.pressureMm!) // I'd advise against every doing force unwrapping on results from a third party service
dataArray.append(weatherData)
group.leave()
}
dataTask.resume()
}
}
group.notify(queue: .main) {
completionHandler(dataArray)
}
}
顺便说一句,在上面,我做了两个不相关的 GCD 更改,即:
FWIW,当您解决当前问题时,您可能需要考虑另外两件事:
如果检索多个位置的详细信息,您可能希望将其限制为一次仅运行一定数量的请求(并避免后者超时)。一种方法是使用非零信号量:
DispatchQueue.global().async {
let semaphore = DispatchSemaphore(value: 4)
for i in ... {
semaphore.wait()
someAsynchronousProcess(...) {
...
semaphore.signal()
}
}
}
如果您过去使用过信号量,这可能会感觉倒退(在发出信号之前等待;大声笑),但非零信号量将使其中四个启动,而其他信号量将作为前四个单独完成/信号启动。
此外,因为我们现在正在等待,我们必须重新将调度引入后台队列以避免阻塞。
同时运行异步请求时,它们可能不会按照您启动它们的顺序完成。如果您希望它们以相同的顺序排列,一种解决方案是在结果完成时将结果存储在字典中,然后在notify
块中构建结果的排序数组:
var results: [Int: Foo] = [:]
// start all the requests, populating a dictionary with the results
for (index, city) in cities.enumerated() {
group.enter()
someAsynchronousProcess { foo in
results[i] = foo
group.leave()
}
}
// when all done, build an array in the desired order
group.notify(queue: .main) {
let array = self.cities.indices.map { results[$0] } // build sorted array of `[Foo?]`
completionHandler(array)
}
这就引出了关于如何处理错误的问题,因此您可以将其设为一个可选数组(如下所示)。
把它放在一起,也许:
func fetchWeatherForCities(completionHandler: @escaping ([YandexWeatherData?]) -> Void) {
DispatchQueue.global().async {
var results: [Int: YandexWeatherData] = [:]
let semaphore = DispatchSemaphore(value: 4)
let group = DispatchGroup()
for (index, city) in self.cities.enumerated() {
group.enter()
semaphore.wait()
var urlString = self.urlString
self.locationManager.getCoordinate(forCity: city) { coordinate in
urlString += self.latitudeField + coordinate.latitude
urlString += self.longitudeField + coordinate.longitude
guard let url = URL(string: urlString) else {
semaphore.signal()
group.leave() // make sure to `leave` in early exit
return
}
var request = URLRequest(url: url)
request.addValue(self.apiKey, forHTTPHeaderField: self.apiField)
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
defer {
semaphore.signal()
group.leave() // make sure to `leave`, whether successful or not
}
guard
let data = data,
error == nil,
let weatherData = self.parseJSON(withData: data)
else {
print(error ?? "unknown error")
return
}
results[index] = weatherData
}
dataTask.resume()
}
}
group.notify(queue: .main) {
let array = self.cities.indices.map { results[$0] } // build sorted array
completionHandler(array)
}
}
}