在 SwiftUI 中异步请求完成后如何调用函数?
How to call function after async requests finish in SwiftUI?
我有一个函数调用 2 种类型的 api 请求来获取我在我的应用程序中需要的一堆数据。在函数中,我发出位置请求,然后针对响应中的每个位置,我发出不同的请求以获取该特定位置的详细信息。 (例如,如果请求 1 returns 20 个位置,我的第二个请求被调用 20 次,每个位置一次)
我的函数代码在这里:
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)
}
}
}
}
我上面引用的请求函数在这里:
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()
}
在我获得我请求的所有数据后(或者甚至在数据传入时),我想将它附加到我在 SceneDelegate.swift 文件中设置的 @EnvironmentObject(数组)。我在我的应用程序的多个位置使用数据,因此 @EnvironmentObject 用作 'source of truth'.
我尝试使用下面的代码完成此操作,但不断收到错误 - "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)
}
}
}
}
我认为我需要确保请求完成然后尝试更新我的@EnvironmentObject,但我不知道该怎么做。
编辑 - 按照评论中的要求提供我的 VenueData 结构:
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")
]
}
解决方案编辑 - 我尝试在我的第二个 api 请求中使用这段代码,它解决了我的问题,尽管我不明白为什么我需要这样做
DispatchQueue.main.async {
self.venueData.venuesdataarray.append(RESPONSE_DETAILS_HERE)
}
最初我问过,有谁知道在所有请求完成后如何更新我的@EnvironmentObject?
有谁知道为什么我上面的代码片段使一切正常?
我只是想了解我在做什么,如果有人发现这个,也许他们可以学到一些东西
我尝试在我的第二个 api 请求中使用这段代码,它解决了我的问题,尽管我不明白为什么我需要这样做
DispatchQueue.main.async {
self.venueData.venuesdataarray.append(RESPONSE_DETAILS_HERE)
}
最初我问过,有谁知道在所有请求完成后如何更新我的@EnvironmentObject?
有谁知道为什么我上面的代码片段使一切正常?我只是想了解我在做什么,如果有人找到这个,也许他们可以学到一些东西
有几件事不能从后台线程成功完成。其中一些(如 UIKit 内容更改)不会产生错误,但会默默地失败,这更糟糕。您有幸收到了相对具体的错误信息。
错误消息是您无法从后台线程发布更改,需要从主线程执行。
将您的追加包装在 "DispatchQueue.main.async" 内使得该行代码 运行 在主线程上。
就是这样。
这可能可以更简洁地解释。
我有一个函数调用 2 种类型的 api 请求来获取我在我的应用程序中需要的一堆数据。在函数中,我发出位置请求,然后针对响应中的每个位置,我发出不同的请求以获取该特定位置的详细信息。 (例如,如果请求 1 returns 20 个位置,我的第二个请求被调用 20 次,每个位置一次)
我的函数代码在这里:
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)
}
}
}
}
我上面引用的请求函数在这里:
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()
}
在我获得我请求的所有数据后(或者甚至在数据传入时),我想将它附加到我在 SceneDelegate.swift 文件中设置的 @EnvironmentObject(数组)。我在我的应用程序的多个位置使用数据,因此 @EnvironmentObject 用作 'source of truth'.
我尝试使用下面的代码完成此操作,但不断收到错误 - "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)
}
}
}
}
我认为我需要确保请求完成然后尝试更新我的@EnvironmentObject,但我不知道该怎么做。
编辑 - 按照评论中的要求提供我的 VenueData 结构:
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")
]
}
解决方案编辑 - 我尝试在我的第二个 api 请求中使用这段代码,它解决了我的问题,尽管我不明白为什么我需要这样做
DispatchQueue.main.async {
self.venueData.venuesdataarray.append(RESPONSE_DETAILS_HERE)
}
最初我问过,有谁知道在所有请求完成后如何更新我的@EnvironmentObject?
有谁知道为什么我上面的代码片段使一切正常?
我只是想了解我在做什么,如果有人发现这个,也许他们可以学到一些东西
我尝试在我的第二个 api 请求中使用这段代码,它解决了我的问题,尽管我不明白为什么我需要这样做
DispatchQueue.main.async {
self.venueData.venuesdataarray.append(RESPONSE_DETAILS_HERE)
}
最初我问过,有谁知道在所有请求完成后如何更新我的@EnvironmentObject?
有谁知道为什么我上面的代码片段使一切正常?我只是想了解我在做什么,如果有人找到这个,也许他们可以学到一些东西
有几件事不能从后台线程成功完成。其中一些(如 UIKit 内容更改)不会产生错误,但会默默地失败,这更糟糕。您有幸收到了相对具体的错误信息。
错误消息是您无法从后台线程发布更改,需要从主线程执行。
将您的追加包装在 "DispatchQueue.main.async" 内使得该行代码 运行 在主线程上。
就是这样。 这可能可以更简洁地解释。