简体   繁体   English

如何在 swift 中使用 async / await?

[英]How do I use async / await in swift?

Overview: I'm geocoding a user input address to use with mapkit and I am unable to wrap my head around using async / await to manage the delay.概述:我正在对要与 mapkit 一起使用的用户输入地址进行地理编码,但我无法使用 async / await 来管理延迟。

I have a SwiftUI view with a textfield that calls a function onSubmit to start the geocoding process ...我有一个带有文本字段的 SwiftUI 视图,该视图调用一个函数 onSubmit 来启动地理编码过程......

                        TextField("Address ...", text: $searchString)
                        .onSubmit {
                            Task {
                                await switchToMap(address: searchString)
                            }
                        }

My switchToMap function ...我的 switchToMap 函数...

    func switchToMap(address: String) async {
    Task {
      locationManager.convertAddress(address: address)
    }
    if let coordinates = locationManager.addressCoordinates {
        locationManager.jumpToPoint(location: coordinates)
    }else{
        print("Something went wrong")
    }
}

LocationManager functions (as relevant) LocationManager 功能(如相关)

    func getCoordinates(addressString: String, completionHandler: @escaping(CLLocationCoordinate2D, NSError?) -> Void) {
    let geocoder = CLGeocoder()
    geocoder.geocodeAddressString(addressString) { (placemarks, error) in
        if error == nil {
            if let placemark = placemarks?[0] {
                let location = placemark.location!
                completionHandler(location.coordinate, nil)
                return
        }
            print(error?.localizedDescription ?? "")
            completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?)
    }
}
}

func convertAddress(address: String){
    getCoordinates(addressString: address) { (location, error) in
        if error != nil {
            // Error Handler
            print("Error converting address: \(error?.localizedDescription ?? "")")
            return
        }
        DispatchQueue.main.async {
            self.addressCoordinates = location
            //self.jumpToPoint(location: location)
        }
    }
}

So, how can I make use of async / await to make all this work?那么,我怎样才能利用 async / await 来完成所有这些工作呢? I fully expect that there are several issues with this code.我完全希望这段代码有几个问题。

Let us consider CLGeocoder method geocodeAddressString first.让我们首先考虑CLGeocoder方法geocodeAddressString That uses a traditional completion handler pattern.它使用传统的完成处理程序模式。 To use that in an async method, we need to wrap that in a withCheckedThrowingContinuation :要在async方法中使用它,我们需要将其包装在withCheckedThrowingContinuation中:

let geocoder = CLGeocoder()

func placemarks(for string: String) async throws -> [CLPlacemark] {
    try await withCheckedThrowingContinuation { continuation in
        geocoder.cancelGeocode() // cancel prior one if any

        geocoder.geocodeAddressString(string) { placemarks, error in
            guard
                error == nil,
                let placemarks = placemarks?.filter( { $0.location != nil }),
                !placemarks.isEmpty
            else {
                continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult))
                return
            }

            continuation.resume(returning: placemarks)
        }
    }
}

Note, I pulled the CLGeocoder out of the method and instead made it a property so that I could implement the “cancel prior request, if any” logic.请注意,我将CLGeocoder从方法中取出,而是将其设为属性,以便我可以实现“取消先前请求(如果有)”逻辑。

Also note that I am just fetching the array of CLPlacemark rather than the coordinates, but if you want the coordinates of the first one, you can then do:另请注意,我只是获取CLPlacemark的数组而不是坐标,但是如果您想要第一个的坐标,则可以执行以下操作:

func coordinate(for string: String) async throws -> CLLocationCoordinate2D {
    let placemark = try await placemarks(for: string)
        .first { $0.location != nil }

    guard let coordinate = placemark?.location?.coordinate else {
        throw CLError(.geocodeFoundNoResult)
    }

    return coordinate
}

Having shown how to wrap the completion-based method in a continuation, we should note that the CLGeocoder allows cancelation, but the above placemarks(for:) implementation will not responded to the cancelation.展示了如何将基于完成的方法包装在一个延续中,我们应该注意到CLGeocoder允许取消,但上述 placemarks placemarks(for:)实现不会响应取消。 To do that, we need to wrap the above in a withTaskCancellationHandler :为此,我们需要将上述内容包装在withTaskCancellationHandler中:

func placemarks(for string: String) async throws -> [CLPlacemark] {
    try await withTaskCancellationHandler {
        geocoder.cancelGeocode()
    } operation: {
        try await withCheckedThrowingContinuation { continuation in
            geocoder.cancelGeocode() // cancel prior one if any

            geocoder.geocodeAddressString(string) { placemarks, error in
                guard
                    error == nil,
                    let placemarks = placemarks,
                    !placemarks.isEmpty
                else {
                    continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult))
                    return
                }

                continuation.resume(returning: placemarks)
            }
        }
    }
}

You might not need cancelation at this point, but it is simple and one of those things you should probably add for any cancelable process.此时您可能不需要取消,但这很简单,并且您可能应该为任何可取消的过程添加其中之一。


For more information about refactoring old code to adopt Swift concurrency, see WWDC 2021 video Swift concurrency: Update a sample app .有关重构旧代码以采用 Swift 并发的更多信息,请参阅 WWDC 2021 视频Swift 并发:更新示例应用程序

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM