0

I am writing an app that uses Reverse Geocoding and the CLGeocoder command. I am using Xcode 11.3 and Swift 5.

The code I am using is stock standard and works ie. I can perform reverse geocoding fine. The issue is that my app calls the reverse geocoded every 5 minutes to report back on the current address - this works absolutely fine.... however, after about 4 days my app no longer works on my test iPhone. When I say no longer works I mean that I click on the icon to open the app and it returns to the IOS. When I run the app on the simulator I can see that about every 5 minutes the memory consumption goes up by 0.01MB, suggesting that maybe after 4 days the containers memory is full and the app stops working properly.

Thus in summary, my question is has anyone else noticed that when using reverse geocoding over a period of time that their apps fail to run properly.

I know its the reverse geocoding as if I don't call the function then the memory consumption stays static.

I know there are limits on the frequency that you can call reversegeolocation - maybe Apple know its leaky and this is why the limit is there (to detract people from doing what I am doing)???

I have attached the entire stock standard code. I have modified it slightly in that I have taken out the frequency element and now made it so you click a button on the screen to trigger the reversegeolocation.... After about 10 clicks you'll see your memory increase, if you keep doing it you'll continue to see the memory increase... (best to try it every 15 - 20 seconds)...

My code:

import Foundation
import UIKit
import CoreLocation


class ViewController: UIViewController, CLLocationManagerDelegate {

    let locationManager:CLLocationManager = CLLocationManager()

    var currentLatitude: Double = 37.33030778
    var currentLongitude: Double = -122.02896839


    @IBOutlet var myButton: UIButton!



    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        locationManager.delegate = self

        print("Running")

    }


    @IBAction func myButtonPressed(_ sender: Any) {
        // Print inside IBACTION
        print("Inside IBACTION")

        // incease latitude and longitue
        currentLatitude = currentLatitude + 0.10
        currentLongitude = currentLongitude + 0.10
        myReverseGeo()
    }



    func myReverseGeo() {

        let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)

        let geocoder: CLGeocoder = CLGeocoder()

        geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in

            if error != nil {
                let errorString = String(describing: error?.localizedDescription)

                print("Reverse Geocoding failed")
                print(errorString)

                return
            }

            let placemark = placemarks! as [CLPlacemark]

                if placemark.count > 0 {

                    let placemark = placemarks![0]

                    let eventCity = placemark.locality
                    let eventState = placemark.subAdministrativeArea
                    let eventCountry = placemark.country

                    print(eventCity!)
                    print(eventState!)
                    print(eventCountry!)

                }
        })

    }

}

First image prior to doing a Reverse Geolocation

Several minutes in after a number of calls to Reverse Geolocation

After 14 mins the utilisation is up to 12MB, after starting at 10.7MB Any ideas folks on how to stop the memory leak (other than not use reverse geocoding)???

  • Current code (but it's missing end?) shouldn't leak. You are limited by the number of reverse location in n times you can do if I remember correctly. Could it be that. When you says it's not working anymore in 4 days, you mean the app crash at launch or won't launch? Then it might be linked to "default" life cycle of certificate app), or there is an error message shown? – Larme Mar 21 '20 at 08:41
  • I've updated the code to show the entire block, like I said it is standard code. Sure it shouldn't leak but it does. Yes I know there is a number of times you can perform reverse location - that should not cause a memory leak. No idea what 'default life cycle of certificate app' is - I don't believe that's relevant as the leak can be seen within minutes if you monitor the memory consumed... Puzzling... – James Leitch Apr 13 '20 at 22:57
  • Regarding the conspiracy theory that Apple has a deliberate leak to prevent frequent requests: If they wanted to rate limit you, they’d have no compunction about just letting it fail and report an error (just like they do for limiting concurrent requests). – Rob May 22 '20 at 20:49
  • Hi Rob and Larme, whilst I still have the issue with the app not restarting once terminated, I have done a whole heap of reading since I posted this item back in march. Thank you both for your feedback / comments - I am pursuing every angle at the moment to see if I can hone in on the issue of the app failing to restart after 4 days. I am also of the opinion now that it has something to do with app lifecycle but I am not sure what at this juncture. I'll post an update as soon as I nail it (hopefully soon as I am keen to publish my app). – James Leitch May 24 '20 at 03:34

1 Answers1

0

I don’t think your memory issues are coming from this routine. I ran it for 144 iterations (for 12 minutes, firing ever 5 seconds; equivalent to running it for 12 hours every 5 minutes) and saw no appreciable memory growth:

enter image description here

(Each of those “points of interest” signposts mark a reverse geocode request.)

A few observations, though:

  1. Rather than instantiating a new CLGeocoder for every request, I’d instantiate one, and perform repeated reverseGeocodeLocation call with that one instance.

  2. Regarding limitations on CLGeocoder, the main limitation is you cannot perform simultaneous geocode requests (and if you attempt to do so, it just fails with an error).

  3. Re why you’re getting memory growth, I can’t comment on that, because I cannot reproduce the behavior you describe. Perhaps you have some debugging options turned on (malloc stack traces, zombies, etc.). That would yield the behavior you describe. Turn off all optional memory diagnostics and perform a release build.

  4. If memory is growing, there are two tools that are useful:

    • The “Debug Memory Graph” will show you objects that have been created and you can look at what strong references have been established where.

    • If you profile the app using the “Leaks” tool (which also provides the “Allocations” tool) in Instruments, you can examine exactly what allocations have been created where.
       

    For more information, see How to debug memory leaks when Leaks instrument does not show them?

  5. Generally, if we’re looking for updates to user locations, the visits service and the significant change service are more efficient ways of doing this. Why drain the user’s battery by doing this every five minutes, when there are going to be long spans where the user doesn’t move. Let the device tell your app when the user moves rather than polling constantly.


It’s not terribly relevant, but this is my code that generated the above image:

var counter = 0
var simulatedTimeInterval: TimeInterval = 0
let maxCounter = 12 * 60 / 5 // how many geocode request in 12 hours, every five minutes

let formatter: DateComponentsFormatter = {
    let formatter = DateComponentsFormatter()
    formatter.unitsStyle = .full
    return formatter
}()

func startTimer() {
    Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] timer in
        guard
            let self = self,
            self.counter < self.maxCounter
        else {
            timer.invalidate()
            return
        }

        self.counter += 1
        self.simulatedTimeInterval += 60 * 5 // add 5 minutes
        let simulatedTimeString = self.formatter.string(from: self.simulatedTimeInterval)!

        os_signpost(.event, log: pointsOfInterest, name: "Geocode", "Request #%d @ %{public}@", self.counter, simulatedTimeString)

        self.updateLocation()
        self.myReverseGeo()
    }
    print("Running")
}

func updateLocation() {
    currentLatitude += 0.010
    currentLongitude += 0.010
}

func myReverseGeo() {
    let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)

    let geocoder = CLGeocoder()

    geocoder.reverseGeocodeLocation(location) { placemarks, error in
        guard
            error == nil,
            let placemark = placemarks?.first,
            let city = placemark.locality,
            let state = placemark.administrativeArea,
            let country = placemark.country
        else {
            print("Reverse Geocoding failed")
            print(error ?? "No placemarks found")
            return
        }

        print(city, state, country)
    }
}

With

import os.signpost

let pointsOfInterest = OSLog(subsystem: "Geocode", category: .pointsOfInterest)
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hi Rob, firstly many thanks for writing back and also all your valued comments and feedback on the code. Since I posted this item I have been doing a LOT more reading on application lifecycle. Whist the problem still exists ie. the app will not restart after about 4 days (if the app is killed with swipe up method) - I am of the feeling that it has nothing to do with the reverse geocode stuff. I am still investigating. The problem is I have to load the code to my device and keep testing daily with either terminations or putting in background before the app fails (usually at 4 days).. – James Leitch May 24 '20 at 03:29