问题描述
我正在尝试使用 dispatchGroup 从多个请求中获取数据。 我不明白为什么 print(weatherData.fact.pressuremm!) 正在工作,但数据没有附加到 dataArray 中,并且 print(dataArray?[0].fact.pressuremm ?? "nil") 打印 nil。
我也尝试从 complitionHandeler 打印数据,结果是一样的。
如何在数组中附加weatherData并正确地从complition中获取值?
func fetchWeatherForCities (complitionHandeler: @escaping([YandexWeatherData]?)->Void) {
var dataArray: [YandexWeatherData]?
let group = dispatchGroup()
for city in cities {
group.enter()
dispatchQueue.global().async {
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 {return}
var request = URLRequest(url: url)
request.addValue(self.apiKey,forHTTPHeaderField: self.apiField)
let dataTask = URLSession.shared.dataTask(with: request) { (data,response,error) in
if let error = error {
print(error)
}
if let data = data {
guard let weatherData = self.parseJSON(withData: data) else {return}
print(weatherData.fact.pressuremm!)
dataArray?.append(weatherData)
print(dataArray?[0].fact.pressuremm ?? "nil")
group.leave()
}
}
dataTask.resume()
}
}
}
group.notify(queue: dispatchQueue.global()) {
complitionHandeler(dataArray)
}
}
解决方法
一些问题:
-
您有执行路径,如果发生错误,您不会调用
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 更改,即:
-
删除了将网络请求分派到全局队列的操作。网络请求已经是异步的,因此分派请求的创建和请求的启动有点多余。
-
在您的
notify
块中,您使用的是全局队列。如果您确实需要,您当然可以这样做,但很可能您将要更新模型对象(如果您从后台队列执行此操作,则需要同步)和 UI 更新。如果你只是将它分派到主队列,生活会更轻松。
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,error in
defer {
semaphore.signal()
group.leave() // make sure to `leave`,whether successful or not
}
guard
let data = data,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)
}
}
}