【问题标题】:Swift async/await in a for loop or mapSwift async/await 在 for 循环或映射中
【发布时间】:2022-02-09 21:50:49
【问题描述】:

我有这个模型:

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 $0.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 吗?文档对此非常困惑,对于这个简单的示例,我将如何做到这一点?

【问题讨论】:

  • 所以您只想将映射[Property] 压缩到[CLLocationCoordinate2D]?我猜你展示的是 3 次尝试解决同一个问题(而不是 3 个单独的问题)?
  • 是的。我展示了 3 种不同的尝试以及它们给出的错误。因为我的coordinates var 是异步的,因为geocodeAddressString 是异步的。我想获取一个坐标数组,然后作为地图注释放置。
  • 我不知道为什么一个答案被删除了,但幸运的是我在我必须出去之前设法通过屏幕抓取它并且它被删除了,因为它确实可以完成这项工作。
  • 我删除了它,因为我对您的情况做了一些可能不正确的假设。您可能希望映射坐标与属性的顺序相同,对吧?使用TaskGroup 并不能保证该订单。我没有注意您正在做的事情的一般背景(添加注释),并且不认为顺序很重要。
  • 啊,是的。我很快意识到我需要Property 本身的这些链接/成员来使用它的其他属性。我想我可能需要看看让Property 计算init() 上的坐标

标签: swift for-loop async-await concurrency


【解决方案1】:

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

  • 最多为任何一项用户操作发送一个地理编码请求。

  • 如果用户执行多个涉及对同一位置进行地理编码的操作,请重复使用初始地理编码请求的结果,而不是为每个操作启动单独的请求。

  • 当您想要自动更新用户的当前位置时(例如当用户移动时),只有在用户移动了相当长的距离并且经过了合理的时间后才发出新的地理编码请求。例如,在典型情况下,您每分钟不应发送超过一个地理编码请求。

而老的Location and Maps Programming Guide 说:

相同的CLGeocoder 对象可用于发起任意数量的地理编码请求,但对于给定的地理编码器,一次只能激活一个请求。

因此,快速发出一系列地理定位请求的整个想法可能是不谨慎的,即使您只做几个,我也倾向于避免同时执行它们。所以,我会考虑一个简单的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
            ...
        }
    }
}

我并不喜欢这种方法(因为我们在计算属性中隐藏了具有各种约束的速率限制 CLGeocoder 请求;如果您重复访问同一属性,将发出重复的地理编码器请求,Apple 明确表示建议我们避免)。但是async 属性在技术上也有效。


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

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

【讨论】:

  • 谢谢你。我从中拿走了很多有用的东西。关于我的循环for x in xx,正如你所建议的那样工作正常,而我使用的xx.forEach 却不行,所以这是一个简单的修复。我将添加一些缓存以确保在成功返回后不执行地理编码。
  • 我也没有考虑让我的Property 符合MKAnnotation 本身,并实现了一个子类来完成这项工作。谢谢
猜你喜欢
  • 2018-01-12
  • 1970-01-01
  • 2021-10-15
  • 1970-01-01
  • 2019-07-16
  • 1970-01-01
  • 2021-08-13
  • 2020-09-04
  • 1970-01-01
相关资源
最近更新 更多