Swift async/await 在 for 循环或映射中

Swift async/await in a for loop or map

我有这个型号:

struct ExactLocation {
    var coordinates: CLLocationCoordinate2D? {
        get async throws {
            let geocoder = CLGeocoder()
            let placemark = try await geocoder.geocodeAddressString(address)
            let mark = MKPlacemark(placemark: placemark.first!)
            return mark.coordinate
        }
    }
}
struct Property {
    let exactLocation: ExactLocation?
}

我正在尝试遍历 Property 数组以使用 Swift 5.5 async/await.

获取所有坐标
private func addAnnotations(for properties: [Property]) async {
    let exactLocations = try? properties.compactMap { try [=13=].exactLocation?.coordinates } <-- Error: 'async' property access in a function that does not support concurrency

    let annotations = await properties.compactMap { property -> MKPointAnnotation? in
        if let exactLocation = property.exactLocation {
            if let coordinates = try? await exactLocation.coordinates {
                
            }
        }
    } <-- Error: Cannot pass function of type '(Property) async -> MKPointAnnotation?' to parameter expecting synchronous function type

    properties.forEach({ property in
        if let exactLocation = property.exactLocation {
            if let coordinates = try? await exactLocation.coordinates {
                
            }
        }
    } <-- Error: Cannot pass function of type '(Property) async -> Void' to parameter expecting synchronous function type
}

那么如何使用异步函数遍历此数组? 我需要创建一个 AsyncIterator 吗?文档对此很混乱,对于这个简单的例子我该如何做?

首先,请注意您执行的请求数。 docs say:

  • Send at most one geocoding request for any one user action.

  • If the user performs multiple actions that involve geocoding the same location, reuse the results from the initial geocoding request instead of starting individual requests for each action.

  • When you want to update the user’s current location automatically (such as when the user is moving), issue new geocoding requests only when the user has moved a significant distance and after a reasonable amount of time has passed. For example, in a typical situation, you should not send more than one geocoding request per minute.

旧的 Location and Maps Programming Guide 说:

The same CLGeocoder object can be used to initiate any number of geocoding requests but only one request at a time may be active for a given geocoder.

因此,快速发出一系列地理定位请求的整个想法可能是轻率的,即使你只做几个,我也倾向于避免同时执行它们。所以,我会考虑一个简单的 for 循环,例如:

func addAnnotations(for addresses: [String]) async throws {
    let geocoder = CLGeocoder()
    
    for address in addresses {
        if 
            let placemark = try await geocoder.geocodeAddressString(address).first,
            let coordinate = placemark.location?.coordinate
        {
            let annotation = MKPointAnnotation()
            annotation.title = placemark.name
            annotation.coordinate = coordinate
            
            // ...
            
            // you might even want to throttle your requests, e.g.
            //
            // try await Task.sleep(nanoseconds: nanoseconds) 
        }
    }
}

从技术上讲,您可以使用计算 属性 方法。现在,我在你的模型中的任何地方都没有看到 address,但让我们想象一下:

struct Property {
    let address: String
    
    var coordinate: CLLocationCoordinate2D? {
        get async throws {
            try await CLGeocoder()
                .geocodeAddressString(address)
                .first?.location?.coordinate
        }
    }
}

(注意强制展开运算符的消除和另一个地标的不必要实例化。)

那么你可以这样做:

func addAnnotations(for properties: [Property]) async throws {
    for property in properties {
        if let coordinate = try await property.coordinate {
            let annotation = MKPointAnnotation()
            annotation.coordinate = coordinate
            ...
        }
    }
}

我对这种方法并不着迷(因为我们将具有各种约束的 rate-limited CLGeocoder 请求隐藏在计算 属性 中;如果您访问相同的 属性 重复,将发出重复的地理编码器请求,Apple 明确建议我们避免)。但是 async 属性 在技术上也有效。


通常在处理注释时,我们希望能够与地图上的注释视图进行交互,并了解它们与哪个模型对象相关联。出于这个原因,我们通常会在注释和模型对象之间保留某种交叉引用。

如果 Property 是引用类型,我们可能会使用一个 MKPointAnnotation 子类来保留对适当 Property 的引用。或者我们可能只是让我们的 Property 符合 MKAnnotation 本身,从而消除了注释和单独模型对象之间引用的需要。有很多方法可以解决这个要求,我不确定我们是否有足够的信息来为您提供适合您情况的正确模式建议。