问题
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