简体   繁体   中英

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> . "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> ). Finally, you can convert the task group to an array using reduce<\/code> , and await<\/code> that:

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.

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:

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:

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). But the async property technically works, too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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