-1

我正在尝试使用 DispatchGroup 从多个请求中获取数据。我不明白为什么 print(weatherData.fact.pressureMm!) 正在工作,但数据没有附加到 dataArray 和 print(dataArray?[0].fact.pressureMm ?? "nil") 打印为零。

我也尝试从 complitionHandeler 打印数据,结果是一样的。

我如何在数组中附加 weatherData 并正确地从编译中获取值?

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)
    }
}
4

1 回答 1

2

几个问题:

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

  2. 您定义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,当您解决当前问题时,您可能需要考虑另外两件事:

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

    DispatchQueue.global().async {
        let semaphore = DispatchSemaphore(value: 4)
    
        for i in ... {
            semaphore.wait()
    
            someAsynchronousProcess(...) {
                ...
    
                semaphore.signal()
            }
        }
    }
    

    如果您过去使用过信号量,这可能会感觉倒退(在发出信号之前等待;大声笑),但非零信号量将使其中四个启动,而其他信号量将作为前四个单独完成/信号启动。

    此外,因为我们现在正在等待,我们必须重新将调度引入后台队列以避免阻塞。

  2. 同时运行异步请求时,它们可能不会按照您启动它们的顺序完成。如果您希望它们以相同的顺序排列,一种解决方案是在结果完成时将结果存储在字典中,然后在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)
        }
    }
}
于 2021-01-25T17:28:10.050 回答