调度组不返回获取的数据 [英] Dispatch group don't return fetched data
问题描述
我正在尝试使用DispatchGroup从多个请求中获取数据.我不明白为什么print(weatherData.fact.pressureMm!)可以正常工作,但是数据没有追加到dataArray和print(dataArray?[0] .fact.pressureMm ??"nil")中,所以打印为nil.
I'm trying to use DispatchGroup for fetching data from multiple request. I cant understand why print(weatherData.fact.pressureMm!) is working, but data didn't appending inside dataArray and print(dataArray?[0].fact.pressureMm ?? "nil") print nil.
我也尝试从complitionHandeler打印数据,结果是相同的.
Also i'm try print data from complitionHandeler and result was same.
我如何才能将weatherData附加到数组中并正确地从complition中获取值?
How i can append weatherData inside array and get value from complition correctly?
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
.确保每个执行路径,包括每个提前退出",都用leave
离开enter
.
You have paths of execution where, if an error occurred, you would not call
leave
. Make sure every path of execution, including every "early exit", offsets theenter
with aleave
.
您已将 dataArray
定义为可选,但从未对其进行初始化.因此它是 nil
.因此, dataArray?.append(weatherData)
永远不会附加值.
You defined dataArray
to be an optional, but never initialize it. Thus it is nil
. And dataArray?.append(weatherData)
therefore will never append values.
因此,也许:
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更改,即:
As an aside, in the above, I have made two unrelated GCD changes, namely:
-
删除了将网络请求分配到全局队列的操作.网络请求已经是异步的,因此分派请求的创建和该请求的启动有点多余.
Removed the dispatching of the network request to a global queue. Network requests are already asynchronous, so dispatching the creation of the request and the starting of that request is a bit redundant.
在您的 notify
块中,您正在使用全局队列.当然,如果确实需要,您可以这样做,但是最有可能的是,您将要更新模型对象(如果从后台队列中进行同步,则需要同步)和UI更新.如果将其分派到主队列,生活会更轻松.
In your notify
block, you were using a global queue. You certainly can do that if you really need, but most likely you are going to be updating model objects (which requires synchronization if you're doing that from a background queue) and UI updates. Life is easier if you just dispatch that to the main queue.
FWIW,当您解决当前问题时,您可能需要考虑另外两件事:
FWIW, when you get past your current issue, you may want to consider two other things:
-
如果要检索许多位置的详细信息,则可能希望将其限制为一次只运行一定数量的请求(并避免后者超时).一种方法是使用非零信号量:
If retrieving details for many locations, you might want to constrain this to only run a certain number of requests at a time (and avoid timeouts on the latter ones). One way is to use a non-zero semaphore:
DispatchQueue.global().async {
let semaphore = DispatchSemaphore(value: 4)
for i in ... {
semaphore.wait()
someAsynchronousProcess(...) {
...
semaphore.signal()
}
}
}
如果您过去使用过信号灯,则可能会感到倒退(在发信号之前等待;大声笑),但是非零信号灯将使其中四个开始,而其他信号将在前四个信号单独完成/开始时启动.
If you have used semaphores in the past, this might feel backwards (waiting before signaling; lol), but the non-zero semaphore will let four of them start, and others will start as the prior four individually finish/signal.
此外,因为我们正在等待,所以我们必须将调度重新引入后台队列以避免阻塞.
Also, because we are now waiting, we have to re-introduce the dispatch to a background queue to avoid blocking.
同时运行异步请求时,它们可能无法按照启动它们的顺序来完成.如果您希望它们以相同的顺序排列,则一种解决方案是在结果完成时将结果存储在字典中,然后在 notify
块中构建结果的排序数组:
When running asynchronous requests concurrently, they may not finish in the order that you started them. If you want them in the same order, one solution is to store the results in a dictionary as they finish, and in the notify
block, build a sorted array of the results:
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)
}
这引起了有关如何处理错误的问题,因此您可以将其设置为可选数组(如下所示).
That begs the question about how you want to handle errors, so you might make it an array of optionals (like shown below).
将其组合在一起,也许:
Pulling that together, perhaps:
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)
}
}
}
这篇关于调度组不返回获取的数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!