'Swift: Accessing argument of closure stored as variable

I ran into some code for a location manager class and I noticed that there is a variable that holds a closure.

var locationInfoCallBack: ((_ info: LocationInformation) -> ())!

I can't seem to access the underlying arguments from the variable, which would be preferred. Is it possible to retrieve the LocationInformation from the above variable?

My understanding is that by passing in the info parameter like this self.locationInfoCallBack(info) the 'start' function will re-run:

In my experience it is unnecessary to recall locationManager.requestAlwaysAuthorization() or locationManager.startUpdatingLocation()

Here's the code:

class Location: NSObject, CLLocationManagerDelegate {
    static let shared = Location()
    
    let locationManager : CLLocationManager
    var locationInfoCallBack: ((_ info: LocationInformation) -> ())!
       
    override private init() {
        locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters
        super.init()
        locationManager.delegate = self
    }
    
    func start(completion: @escaping(_ info: LocationInformation) -> Void) {
        self.locationInfoCallBack = completion
        locationManager.requestAlwaysAuthorization()
        locationManager.startUpdatingLocation()
    }

    func stop() {
        locationManager.stopUpdatingLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let mostRecentLocation = locations.last else {
            return
        }
       
        let info = LocationInformation()
        info.latitude = mostRecentLocation.coordinate.latitude
        info.longitude = mostRecentLocation.coordinate.longitude


        let geocoder = CLGeocoder()
        geocoder.reverseGeocodeLocation(mostRecentLocation) { (placemarks, error) in
            guard let placemarks = placemarks, let placemark = placemarks.first else { return }
            if let city = placemark.locality,
                let state = placemark.administrativeArea,
                let zip = placemark.postalCode,
                let locationName = placemark.name,
                let thoroughfare = placemark.thoroughfare,
                let country = placemark.country {
                info.city = city
                info.state = state
                info.zip = zip
                info.address = locationName + ", " + (thoroughfare as String)
                info.country = country
            }
            self.locationInfoCallBack(info)
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        locationManager.stopUpdatingLocation()
    }
}

class LocationInformation {
    var city: String?
    var address: String?
    var latitude: CLLocationDegrees?
    var longitude: CLLocationDegrees?
    var zip: String?
    var state: String?
    var country: String?
    
    init(city: String? = "", address: String? = "", latitude: CLLocationDegrees? = Double(0.0), longitude: CLLocationDegrees? = Double(0.0), zip: String? = "", state: String? = "", country: String? = "") {
        self.city = city
        self.address = address
        self.latitude = latitude
        self.longitude = longitude
        self.zip = zip
        self.state = state
        self.country = country
    }
}


If the underlying argument is inaccessible my current hypothesis is the variable 'locationInfoCallBack' could be used to check whether the 'start' function has completed. If this is the case why not use a simple boolean for the same result?



Solution 1:[1]

You can access the parameter when the closure is running from the code of the closure, and its value will be whatever the caller passed. It’s the same as with a function. Unless the function (or closure) is running, the parameters don’t exist.

Solution 2:[2]

I want it to be available app wide

In that case, a more suitable design would be not to use a closure. Replace the closure with:

var currentLocationInfo: LocationInformation?

The parameter to start can be removed:

func start() {
    locationManager.requestAlwaysAuthorization()
    locationManager.startUpdatingLocation()
}

And in didUpdateLocation, replace the call self.locationInfoCallBack(info) with a simple assignment to currentLocationInfo:

self.currentLocationInfo = info

Now you can access from anywhere:

Location.shared.currentLocationInfo

This will give you the location that you got, the last time that didUpdateLocation is called, and you successfully reverse-geocoded that.

Note that this could be nil, because you might be accessing it before the first time that you successfully. reverse-geocoded a location.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 gnasher729
Solution 2 Sweeper