How to call function after async requests finish in SwiftUI?

倖福魔咒の 提交于 2020-05-16 08:59:40

问题


I have a function that calls 2 types of api requests to get a bunch of data i need in my app. In the function I make a request for locations, then for each location in the response I make a different request to get details of that specific location. (ex. if request 1 returns 20 locations, my second request is called 20 times, once for each location)

My function code here:

    func requestAndCombineGData(location: CLLocation, radius: Int) {
    // Clears map of markers
        self.mapView.clear()


        // Calls 'Nearby Search' request 
        googleClient.getGooglePlacesData(location: location, withinMeters: radius) { (response) in
            print("Made Nearby Search request. Returned response here:", response)

            // loops through each result from the above Nearby Request response
            for location in response.results {

                // Calls 'Place Details' request
                self.googleClient.getGooglePlacesDetailsData(place_id: location.place_id) { (detailsResponse) in
                    print("GMV returned - detailsResponse.result - ", detailsResponse.result)

                }

            }            
        }

    }

Request functions I reference above are here:

    func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: @escaping (GooglePlacesResponse) -> ())  {

        for category in categoriesArray {

            let url = googlePlacesNearbyDataURL(forKey: googlePlacesKey, location: location, radius: radius, type: category)

            let task = session.dataTask(with: url) { (responseData, _, error) in
                if let error = error {
                    print(error.localizedDescription)
                    return
                }

                guard let data = responseData, let response = try? JSONDecoder().decode(GooglePlacesResponse.self, from: data) else {
                    print("Could not decode JSON response")
                    completionHandler(GooglePlacesResponse(results:[]))
                    return
                }


                if response.results.isEmpty {
                    print("GC - response returned empty", response)
                } else {
                    print("GC - response contained content", response)
                    completionHandler(response)
                }


            }
            task.resume()
        }

    }




    func getGooglePlacesDetailsData(place_id: String, using completionHandler: @escaping (GooglePlacesDetailsResponse) -> ())  {

        let url = googlePlacesDetailsURL(forKey: googlePlacesKey, place_ID: place_id)

        let task = session.dataTask(with: url) { (responseData, _, error) in
            if let error = error {
                print(error.localizedDescription)
                return
            }

            guard let data = responseData, let detailsResponse = try? JSONDecoder().decode(GooglePlacesDetailsResponse.self, from: data) else {
                print("Could not decode JSON response. responseData was: ", responseData)
                return
            }
            print("GPD response - detailsResponse.result: ", detailsResponse.result)


            completionHandler(detailsResponse)


        }
        task.resume()

    }

After I get all the data im requesting (or even as the data is coming in) I would like to append it to an @EnvironmentObject (array) I have set up in my SceneDelegate.swift file. Im using the data in multiple places in my app so the @EnvironmentObject serves as a 'source of truth'.

I tried accomplishing this using the code below, but keep getting the error - "Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates."

    func requestAndCombineGData(location: CLLocation, radius: Int) {
    // Clears map of markers
        self.mapView.clear()


        // Calls 'Nearby Search' request 
        googleClient.getGooglePlacesData(location: location, withinMeters: radius) { (response) in
            print("Made Nearby Search request. Returned response here:", response)

            // loops through each result from the above Nearby Request response
            for location in response.results {

                // Calls 'Place Details' request
                self.googleClient.getGooglePlacesDetailsData(place_id: location.place_id) { (detailsResponse) in
                    print("GMV returned - detailsResponse.result - ", detailsResponse.result)


                // THIS IS WHERE I TRY TO UPDATE MY @ENVIROMETOBJECT
                self.venueData.venuesdataarray.append(detailsRespose.result)


                }

            }            
        }

    }

I believe I need to make sure the requests complete THEN try to update my @EnvironmentObject, but I do not know how to do that.


EDIT - providing my VenueData struct as requested in comments:

struct VenueData : Identifiable {

    var id = UUID()
    var name : String
    var geometry : Location?
    var rating : String?
    var price_level : String?
    var types : [String]?
    var formatted_address : String?
    var formatted_phone_number : String?
    var website : String?
    var photo_reference : String?


    enum CodingKeysDetails : String, CodingKey {
        case geometry = "geometry"
        case name = "name"
        case rating = "rating"
        case price_level = "price_level"
        case types = "types"
        case opening_hours = "opening_hours"
        case formatted_address = "formatted_address"
        case formatted_phone_number = "formatted_phone_number"
        case website = "website"
    }


    // Location struct
    struct Location : Codable {

        var location : LatLong

        enum CodingKeys : String, CodingKey {
            case location = "location"
        }


        // LatLong struct
        struct LatLong : Codable {

            var latitude : Double
            var longitude : Double

            enum CodingKeys : String, CodingKey {
                case latitude = "lat"
                case longitude = "lng"
            }
        }
    }



}


class VenueDataArray: ObservableObject {
    @Published var venuesdataarray : [VenueData] = [
        VenueData(name: "test_name")
    ]
}

Solution Edit - I tried using this snippet of code within my second api request and it solved my issue, although i do not understand why I need to do this

    DispatchQueue.main.async {
        self.venueData.venuesdataarray.append(RESPONSE_DETAILS_HERE)
    }

Originally I had asked, Does anyone know how I can update my @EnvironmentObject after all the requests complete?

Does anyone know why the snippet I have above makes everything work?

Id just like to understand what im doing and maybe someone could learn something if they find this


回答1:


I tried using this snippet of code within my second api request and it solved my issue, although i do not understand why I need to do this

DispatchQueue.main.async {
    self.venueData.venuesdataarray.append(RESPONSE_DETAILS_HERE)
}

Originally I had asked, Does anyone know how I can update my @EnvironmentObject after all the requests complete?

Does anyone know why the snippet I have above makes everything work? Id just like to understand what im doing and maybe someone could learn something if they find this




回答2:


There are several things that you cannot successfully do from a background thread. Some of them (like UIKit content changes) do not generate an error, but fail silently which is worse. You have the good fortune to have received a relatively specific error message.

The error message was that you couldn't publish changes from a background thread and needed to do that from the main thread.

Wrapping your append inside "DispatchQueue.main.async" makes that line of code run on the main thread.

That's it. This could probably have been explained more concisely.



来源:https://stackoverflow.com/questions/59619673/how-to-call-function-after-async-requests-finish-in-swiftui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!