I'm trying to create a function that takes a postalCode and passes back the city, state, and country. The function finds the results and they can be printed from the calling closure; however, when I save the data outside they closure, they disappear.
Here's my code:
func getAddress(forPostalCode postalCode: String, completion: @escaping (_ city: String?, _ state: String?, _ country: String?, _ error: Error?) -> Void) {
let geocoder = CLGeocoder()
let addressString = "\(postalCode), USA"
geocoder.geocodeAddressString(addressString) { placemarks, error in
if let error = error {
completion(nil, nil, nil, error)
return
}
guard let placemark = placemarks?.first else {
completion(nil, nil, nil, NSError(domain: "com.example.app", code: 1, userInfo: [NSLocalizedDescriptionKey: "No placemarks found"]))
return
}
guard let city = placemark.locality else {
completion(nil, nil, nil, NSError(domain: "com.example.app", code: 2, userInfo: [NSLocalizedDescriptionKey: "City not found"]))
return
}
guard let state = placemark.administrativeArea else {
completion(nil, nil, nil, NSError(domain: "com.example.app", code: 3, userInfo: [NSLocalizedDescriptionKey: "State not found"]))
return
}
guard let country = placemark.country else {
completion(nil, nil, nil, NSError(domain: "com.example.app", code: 4, userInfo: [NSLocalizedDescriptionKey: "Country not found"]))
return
}
completion(city, state, country, nil)
}
}
let postalCode = "10001"
var aCity: String = ""
var aState: String = ""
var aCountry: String = ""
getAddress(forPostalCode: postalCode) { city, state, country, error in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
if let city = city, let state = state, let country = country {
aCity = city
aState = state
aCountry = country
print("Internal: \(aCity), \(aState) in \(aCountry)") }
else {
print("Error: Unable to retrieve address for postal code \(postalCode)")
}
}
print("External: \(aCity), \(aState) in \(aCountry)")
Here are the results I get:
External: , in
Internal: New York, NY in United States
The
getAddressis an asynchronous function. It returns immediately, but itscompletionclosure is called asynchronously (i.e., later). Your three variables are not populated by the timegetAddressreturns, but only later. This is how the asynchronous completion-handler pattern works. If you search the web for “swift completion handler”, you will find many good discussions on this topic.It’s also one of the reasons that
async-awaitof Swift concurrency is so attractive, that it eliminates this silliness. If you find this completion handler pattern confusing, consider adopting Swift concurrency.If you are interested in learning about Swift concurrency, I might suggest watching WWDC 2021 video Meet async/await in Swift. On that page, there are links to other Swift concurrency videos, too.
For the sake of comparison, there is an
asyncrendition ofgeocodeAddressString. E.g.:This eliminates the complicated reasoning that the completion handler closure pattern entails. It gives you code that is linear and logical in its flow, but also is asynchronous and avoids blocking any threads.
As an aside, note that I chose to avoid the three separate variables of city, state, and zip and wrapped that in an
Addressobject:Note, I also avoid the use of
NSErrorand use my own custom error type.