调度组不返回获取的数据 [英] Dispatch group don't return fetched data

查看:67
本文介绍了调度组不返回获取的数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用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)
    }
}

推荐答案

一些问题:

  1. 您具有执行路径,如果发生错误,则不会调用 leave .确保每个执行路径,包括每个提前退出",都用 leave 离开 enter .

  1. 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 the enter with a leave.

您已将 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:

  1. 如果要检索许多位置的详细信息,则可能希望将其限制为一次只运行一定数量的请求(并避免后者超时).一种方法是使用非零信号量:

  1. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆