Update: It should be noted that the question below is of academic nature and the use of core location, or the polling of location data is not relevant to the question - the proper way to do this is always through the core location delegate method. My original question eventually boiled down to: "Is infinite recursion ever possible in swift? (or tail recursion)". The answer to this is no. This is what caused my error due to stack space exhaustion.
Original question: I'm having an issue with a recursive function that passes values through a closure. I'm a longtime Objective-C developer but have not been programming in Swift long, so maybe I'm missing something obvious. Here is the function and how I'm calling it:
public func getLocation(completion: @escaping (CLLocationCoordinate2D?) -> ())
{
completion(self.currentLocation)
weak var weakSelf = self
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
weakSelf?.getLocation(completion: {location in
completion(weakSelf?.currentLocation)
})
}
}
LocationManager.shared.getLocation(completion: {location in
if(location != nil)
{
weakSelf?.lonLabel.text = "Lat: " + location!.latitude.description
weakSelf?.latLabel.text = "Lon: " + location!.longitude.description
}
})
The bug (description Thread 1: EXC_BAD_ACCESS (code=2, address=0x16f1b7fd0)
) I'm getting after running for a period of time is this:
What I'm trying to accomplish is pass an auto-updating location value to a location manager object. I'm thinking another way to accomplish is with performSelector withObject afterDelay
but at this point, I'm just wondering why this is crashing?
You're causing a stack overflow, by having getLocation(completion:)
call getLocation(completion:)
, which calls getLocation(completion:)
, ... until you run out of stack space.
You could use a DispatchSourceTimer
instead:
import Dispatch
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
timer.schedule(deadline: .now(), repeating: .seconds(5), leeway: .milliseconds(100))
timer.setEventHandler { [weak self] in
guard let strongSelf = self,
let location = LocationManager.shared.currentLocation else { return }
strongSelf.lonLabel.text = "Lat: \(location.latitude)"
strongSelf.latLabel.text = "Lon: \(location.longitude)"
}
timer.resume()
But this whole polling approach just doesn't make any sense. Your location delegate is already being informed of location changes. You can just change your labels then.