Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reverse geocoding should not occur multiple times per session #462

Open
4 tasks
vdhamer opened this issue Nov 20, 2024 · 0 comments
Open
4 tasks

Reverse geocoding should not occur multiple times per session #462

vdhamer opened this issue Nov 20, 2024 · 0 comments
Assignees
Labels
Concurrency Swift structured concurrency & Swift6 data race prevention Enhancement New feature or request

Comments

@vdhamer
Copy link
Owner

vdhamer commented Nov 20, 2024

If the user scrolls up and down within the Clubs and Museums screen, any map that gets displayed triggers (.onAppear) an asynchronous call to reverseGeocode. This fetches localized Town and Country strings that match the current language setting of the device.

This use of onAppear means the app can send multiple identical requests to Apple's mapping server. Even once is a bit more than needed: the results are stored in Core Data. But recomputing .onAppear means that any club/museum coordinate updates or a change in the device's language setting are automatically handled just before the data is displayed.

Drawbacks of multiple calls to reverseGeocode

  • extra calls = extra bandwidth
  • town and country can disappear ("Town?") if the network connection is unreliable (why/how?)
  • the server's API is rate limited by Apple
            .onAppear {
                // on main queue (avoid accessing NSManagedObjects on background thread!)
                let clubName = filteredOrganization.fullName
                let town = filteredOrganization.town // unlocalized
                let coordinates = filteredOrganization.coordinates

                Task.detached { // other (non-bgContext) background thread to access 2 async functions
                    var localizedTown: String
                    var localizedCountry: String?
                    do {
                        let (locality, nation) = // can be (nil, nil) for Chinese location or Chinese user location
                            try await reverseGeocode(coordinates: coordinates)
                        localizedTown = locality ?? town // unlocalized as fallback for localized -> String
                        localizedCountry = nation // optional String
                        await updateTownCountry(clubName: clubName, town: town,
                                                localizedTown: localizedTown, localizedCountry: localizedCountry)
                    } catch {
                        print("ERROR: could not reverseGeocode (\(coordinates.latitude), \(coordinates.longitude))")
                    }
                }
            }

Alternative solutions

  1. Store (in volatile static dictionary?) the list of clubs that have already been reverseGeocoded in this session.
    ⇒ API usage is one call per club per session.
    Verdict: not enough benefit for the effort?

  2. Store (Core Data) the date when localizedTown and localizedCountry were last refreshed. Until date+N days, we assume any available translations are stil valid, and an API call is saved.
    Needs special handling at startup if the current locale matches the previous locale (UserData). If there is no match, all translations stored in Core Data are invalidated (by changing the date or setting the strings to nil).
    Needs special handling (in the unlikely case) that the coordinates of an existing club are changed: also invalidate that record.
    The locale/coordinate guarding should handle normal updating; the date trick is just to handle sever-side data changes.
    Multiple clubs can get invalidated on the same day if they were first loaded/viewed on the same day. This can be handled by spreading the load across a few days (Int.random(0...10))
    ⇒ API usage is one call per club per N days. Say N=100 days.

  3. Same as 2, but without the date-based safety net.
    ⇒ API usage is one call per club (unless an update is needed, close to absolute minimum).
    Can serve as intermediate step to get to 2.

  4. Same as 2, but with with the strings stored in UserData instead of CoreData organization objects.
    Implement as a cache on the reverseGeocode(coordinates: coordinates, locale) request, without caring about the Organization being processed. Cache can be stored as a dictionary in UserDefaults. For 1000 organizations and a user who only uses a single language, this is a dictionary with 1000 structs with 5 fields: language, latitude, longitude, localizedTown, localizedCountry.
    If needed, nearby locations (say 250m) can result in cache hits. This will occasionally save API calls if there are multiple organizations in the same building, or if a coordinate shifts slightly (e.g. extra digit of precision added).
    ⇒ API usage is one call per club (unless an update is needed, close to absolute minimum).

  5. Store in CoreData entity the language(s) for which the localized Town/Country was computed. If new language appears, recompute. If coordinates change, force a recompute by setting the language to nil or a dummy language. So this requires only 1 stored field.
    ⇒ API usage is one call per club (unless an update is needed, close to absolute minimum). This is absolute minimum if multiple languages are "cached".

Steps to get to 2nd alternative

  • if localizedTown and localizedCountry are available, use them without a call to the reverseGeocoding API. Causes each club to be translated only once. And not to react to server-side, language or coordinate changes.
  • detect language change at startup and invalidate translations by setting all strings to nil (needs a bool because nil means "ignore")
  • detect a coordinate change and invalidate translations for that organization
  • add a date to force occasional refresh of the cache
@vdhamer vdhamer added Enhancement New feature or request Concurrency Swift structured concurrency & Swift6 data race prevention labels Nov 20, 2024
@vdhamer vdhamer self-assigned this Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Concurrency Swift structured concurrency & Swift6 data race prevention Enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant