Background
A lot of my recent project involve functions with completion handlers returning either an error, or an (optional) object.
That’s the case for most of CloudKit functions. Example:
1 |
func performQuery(query: CKQuery, inZoneWithID zoneID: CKRecordZoneID?, completionHandler: ([CKRecord]?, NSError?) -> Void) |
Or MapKit :
1 2 3 4 |
func reverseGeocodeLocation(location: CLLocation, completionHandler: CLGeocodeCompletionHandler) // with CLGeoCodeCompletionHandler defined like so: typealias CLGeocodeCompletionHandler = ([CLPlacemark]?, NSError?) -> Void |
I’m still learning the new error handling in Swift 2, so I don’t know if these completionHandlers returning NSError? still have a future.
What I know is that the guard statement is quite helpful in this case.
Checking the result with « IF »
Previously, here is how you would check for the result of an asynchronous call:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
func determineElementsForAddress(address: Address) { // Suppose that Address is a class with coordinates (latitude and longitude), and properties such as streetName, locality, postalCode and country geoCoder.reverseGeocodeLocation(CLLocation(latitude: address.coordinate.latitude, longitude: address.coordinate.longitude), completionHandler: { (placemarks: [CLPlacemark]?, error: NSError?) in if error != nil { print("Error in geoCoding: \(error!.localizedDescription)") // we can safely unwrap error here return } else { // We need to unwrap place marks if let placemarks = placemarks { if let placemark = placemarks.first { // Store address elements in the annotation address address.streetName = placemark.name address.locality = (placemark.locality ?? "") address.postalCode = (placemark.postalCode ?? "") address.country = (placemark.country ?? "") } } } }) } |
Two things are kind of cumbersome here :
- I need to address the error case first, with a « if / else » statement. And the « good » case is in the « else » clause of this statement. With more curly braces …
- I need to unwrap the optional array if there is no error. Or I could blindly suppose that, if there is no error then the optional array can be implicitely unwrapped with « ! » (placemarks! everywhere), but you never know …
Checking the result with « GUARD »
The new « guard » statement lets you address the error case in a separate clause, but then the « good » case is in the main part of the function, not in the « else » clause of an « if » statement. Also, you can use optional binding with the « guard » statement.
Therefore, the previous code becomes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func determineElementsForAddress(address: Address) { // Suppose that Address is a class with coordinates (latitude and longitude), and properties such as streetName, locality, postalCode and country geoCoder.reverseGeocodeLocation(CLLocation(latitude: address.coordinate.latitude, longitude: address.coordinate.longitude), completionHandler: { (placemarks: [CLPlacemark]?, error: NSError?) in guard (error == nil), let placemarks = placemarks else { print("Error in geoCoding: \(error!.localizedDescription)") // we can safely unwrap error here return } if let placemark = placemarks.first { // placemarks is automatically unwrapped in this case // Store address elements in the annotation address address.streetName = placemark.name address.locality = (placemark.locality ?? "") address.postalCode = (placemark.postalCode ?? "") address.country = (placemark.country ?? "") } }) } |
I successfully killed two pairs of curly braces! 🙂
I know that I could use optional binding with an « if » statement too, but, as I am testing for the « wrong » case (if error != nil), it gets a bit complicated to mix an optional binding for something I want (if let placemarks = placemarks) with a boolean condition corresponding to something I don’t want (if error != nil).
The way I understand this « guard » statement is: « ensure that there is no error, and that placemarks can be unwrapped. If this is not the case, print the error in the console and get out of there« .
I find the new guard statement to be much more readable and easier to maintain. Once I understood how to use it, it took me only a few minutes to upgrade my « if error != nil » to the new « guard » statement.
PS: you can have several boolean checks in your guard statement, but in that case they have to be separated by « && ». The comma is reserved to unwrap optionals.
PPS: you can get quite complex guard statements, like that:
1 2 3 4 5 6 7 8 9 10 |
let task = session.dataTaskWithRequest(urlRequest) { (data: NSData?, urlResponse: NSURLResponse?, error: NSError?) -> Void in // Check for error guard (error == nil), let httpResponse = urlResponse as? NSHTTPURLResponse where httpResponse.statusCode == 200, let data = data else { print(error?.localizedDescription) return } // Get result in JSON / XML } |