【问题标题】:How to call function after multiple asynchronous api requests finish in SwiftUI?SwiftUI中多个异步api请求完成后如何调用函数?
【发布时间】:2020-04-23 11:21:26
【问题描述】:

我仍在 SwiftUI 中培养我的技能,因此感谢任何有经验的开发人员可以帮助回答我的问题。

对于上下文,我正在构建一个在地图上显示标记的应用程序,以及一个标记列表。我使用@EnvironmentObject 来保存数据,以便有“一个真实来源”,并且地图和列表会在数据更改时自动更新。

为了获取我需要的数据,我提出了 2 种类型的 api 请求。获取位置列表,然后为每个位置请求有关每个位置的额外信息。

为了更新我设置的@EnvironmentObject,我需要完成所有请求,然后从应用程序的主线程更新它。所以我的问题是我需要在 api 请求完成后才能调用函数。

下面的代码显示了我目前如何在我的应用程序中实现 api 请求,但我希望有人能告诉我如何更改它们,以便我可以在 2 个请求完成后调用函数:


这是我的文件的骨架,它显示了一个地图以及我在哪里发出我的 api 请求: 主要部分在底部的“扩展GMController”中

import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation



struct GoogMapView: View {
    var body: some View {
        GoogMapControllerRepresentable()
    }
}



class GoogMapController: UIViewController, CLLocationManagerDelegate {
    var locationManager = CLLocationManager()
    var mapView: GMSMapView!
    let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
    var zoomLevel: Float = 15.0
    let marker : GMSMarker = GMSMarker()


    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestAlwaysAuthorization()
        locationManager.distanceFilter = 50
        locationManager.startUpdatingLocation()
        locationManager.delegate = self

        let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
        mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setMinZoom(14, maxZoom: 20)
        mapView.settings.compassButton = true
        mapView.isMyLocationEnabled = true
        mapView.settings.myLocationButton = true
        mapView.settings.scrollGestures = true
        mapView.settings.zoomGestures = true
        mapView.settings.rotateGestures = true
        mapView.settings.tiltGestures = true
        mapView.isIndoorEnabled = false


        marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
        marker.title = "Boston"
        marker.snippet = "USA"
        marker.map = mapView

        view.addSubview(mapView)

    }

    // Handle incoming location events.
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
      let location: CLLocation = locations.last!
      print("Location: \(location)")

      let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)


        mapView.animate(to: camera)

    }

    // Handle authorization for the location manager.
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
      switch status {
      case .restricted:
        print("Location access was restricted.")
      case .denied:
        print("User denied access to location.")
        // Display the map using the default location.
        mapView.isHidden = false
      case .notDetermined:
        print("Location status not determined.")
      case .authorizedAlways: fallthrough
      case .authorizedWhenInUse:
        print("Location status is OK.")
      }
    }

    // Handle location manager errors.
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
      locationManager.stopUpdatingLocation()
      print("Error: \(error)")
    }

}


// UIViewControllerExtension
extension GMController {


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

    // vvv  THIS IS WHERE I AM TRYING TO CALL THE REQUESTS IN SUCCESSION AND THEN CALL A FUNCTION AFTER THE REQUESTS ARE FINISHED  vvv
        // 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' to get the 'place_id' and make 'Place Details'
            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)

                }
            }            
        }

    }

}



struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
        return GMController()
    }

    func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {

    }
}



有谁知道我可以如何更改我的代码,以便只有在所有能够使用请求数据的 api 请求完成后才能调用函数?

【问题讨论】:

    标签: swift asynchronous request asynccallback


    【解决方案1】:

    假设您从第一个 API 调用开始或在第一个 API 调用结束时调用第二个 API。

    在你的 viewDidLoad() 上添加一个观察者

    NotificationCenter.default.addObserver(self, selector: #selector(
    self.methodOfReceivedNotification(notification:)), name: Notification.Name
    ("NotificationIdentifier"), object: nil)
    

    然后在满足案例或满足条件或呼叫完成时在“位置”api调用上添加发布通知

    NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"),
    object: nil)
    

    然后在里面,写下你想要在定位调用完成后发生的任何事情。

    @objc func methodOfReceivedNotification(notification: Notification) 
    {
    ///
    }
    

    【讨论】:

    • 我的第二个请求被多次调用,具体取决于第一个请求返回的数据量。 (所以如果请求 1 返回 30 个位置,我的第二个请求会为每个位置调用 30 次)。所以真的我想在所有这些都完成后调用一个函数。你认为你的方法仍然是最好的选择吗?
    • @zlyt 您可以将结果存储在一个数组中,并不断将更新的结果添加到该数组中,并在每次调用您的函数时继续更新主线程?还是您的主线程应该只调用 1 次?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多