简体   繁体   English

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

[英]Swift async/await in a for loop or map

I have this model:我有这个模型:

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?
}

You can use a TaskGroup<\/code> here, which is a kind of AsyncSequence<\/code> .您可以在此处使用TaskGroup<\/code> ,它是一种AsyncSequence<\/code> 。 "Getting the coordinates" for each property are the tasks that you should add to a task group.每个属性的“获取坐标”是您应该添加到任务组的任务。 You can then transform the tasks in whatever way you like (eg compactMap<\/code> ).然后,您可以以任何您喜欢的方式转换任务(例如compactMap<\/code> )。 Finally, you can convert the task group to an array using reduce<\/code> , and await<\/code> that:最后,您可以使用reduce<\/code>将任务组转换为数组,然后await<\/code> :

private func addAnnotations(for properties: [Property]) async {
    let coordinates = await withTaskGroup(
        of: CLLocationCoordinate2D?.self) { group -> [CLLocationCoordinate2D] in
        for property in properties {
            group.addTask { try? await property.exactLocation?.coordinates }
        }
        return await group.compactMap { $0 }
        .reduce(into: []) { $0.append($1) }
    }
}

First, be careful about the number of requests that you perform.首先,请注意您执行的请求数量。 The 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.例如,在典型情况下,您每分钟不应发送超过一个地理编码请求。

And the old Location and Maps Programming Guide says:旧的位置和地图编程指南说:

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.相同的CLGeocoder对象可用于启动任意数量的地理编码请求,但对于给定的地理编码器,一次只能激活一个请求。

So, the whole idea of rapidly issuing a series of geolocation requests may be imprudent, and even if you were to do just a few, I would be inclined to avoid performing them concurrently.所以,快速发出一系列地理定位请求的整个想法可能是不谨慎的,即使你只做几个,我也倾向于避免同时执行它们。 So, I would consider a simple for loop, eg:所以,我会考虑一个简单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) 
        }
    }
}

Technically, you could do the computed property approach.从技术上讲,您可以使用计算属性方法。 Now, I did not see address in your model anywhere, but let's imagine:现在,我在任何地方都没有在您的模型中看到address ,但让我们想象一下:

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

(Note the elimination of the forced unwrapping operator and the unnecessary instantiating of another placemark.) (注意强制展开运算符的消除和另一个地标的不必要实例化。)

Then you could do:然后你可以这样做:

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

I am not crazy about that approach (as we are hiding rate-limited CLGeocoder requests with all sorts of constraints inside a computed property; if you access the same property repeatedly, duplicate geocoder requests will be issued, which Apple explicitly advises that we avoid).我对这种方法并不疯狂(因为我们在计算属性中隐藏了具有各种约束的限速CLGeocoder请求;如果您重复访问相同的属性,将发出重复的地理编码器请求,Apple 明确建议我们避免这种情况) . But the async property technically works, too.但是async属性在技术上也有效。

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

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