chemistree apps

ios development

'If let' and thread safety

by Andy Riordan

I have a confession to make. The first time I saw if let syntax, I thought it was ugly, complex, and confusing. However, my opinion has changed, and I wanted to share some of my favorite things about it.

One major advantage to using if let is that it is naturally defensive against threading issues. When I starting using Swift, I saw a lot of third-party code online written like this, and was even tempted to adopt the style myself:

var postTitle: String?

func doSomethingWithAnOptionalValue() {
    if postTitle == nil {
        return
    }
    
    doStuffWith(postTitle!)
}

I can understand the appeal. While the indentation seems tame here, it can get pretty gnarly in more complex examples. The approach above avoids seemingly needless indentation, the creation of a local constant that you need to worry about, etc. Can you see the problem with that approach, however?

func doSomethingWithAnOptionalValue() {
    if postTitle == nil {
        return
    }
    // Let's say postTitle just got assigned nil on a different thread
    doStuffWith(postTitle!) // Crash
}

Yeah, that’s a problem. This brings up a different point - Apple made the ‘forced upwrapping’ operator ! for a reason. It should be something carefully considered, and avoided whenever possible.

In contrast, consider the if let approach:

func doSomethingWithAnOptionalValue() {
    if let unwrappedPostTitle = postTitle {
        doStuffWith(unwrappedPostTitle)
    }
}

If the variable got assigned nil or to another value while we’re inside the if statement, it doesn’t matter. We already have our constant reference pointing to the object the instance variable referred to when the if let was evaluated, and we already know it’s not nil.

I also got annoyed by the funky names I had to come up with for the unwrapped optional, as above. Luckily, Swift solves that for us with its (impressively flexible) scoping:

func doSomethingWithAnOptionalValue() {
    if let postTitle = postTitle {
        doStuffWith(postTitle)
    }
}

postTitle is now scoped to this if statement, so postTitle and self.postTitle are now two entirely different things.

Forward Geocoding

by Andy Riordan

In iOS 5, Apple introduced a great new feature: forward geocoding. Forward here is as opposed to reverse geocoding, which has been present since MapKit was introduced. Where reverse geocoding takes a longitude/latitude pair and converts it to city, state, zip code and so on, forward geocoding takes a city, state, zip code kind of thing and turns it into a lat/long.

First try

Here’s a simple snippet to try forward geocoding on iOS 5. Make sure you have CoreLocation.framework in your Frameworks section, then include the header:

#import <CoreLocation/CoreLocation.h>

Then, to test it out:

CLGeocoder *geocoder = [[CLGeocoder alloc] init];

CLGeocodeCompletionHandler completionHandler = 
^(NSArray *placemarks, NSError *error)
{
    if ([placemarks count] > 0)
    {
        CLPlacemark *placemark = [placemarks objectAtIndex:0];
        
        NSLog(@"Found placemark: %@", placemark);
    }
};

[geocoder geocodeAddressString:@"Outer Mongolia" completionHandler:completionHandler];

Note: For brevity, this snippet leaks geocoder if not run on ARC. The sample code provided releases it correctly.

If you run it in the iOS 5 simulator (or device), it will work great. Turns out Outer Mongolia is really a place:

Forward Geocoding[1902:f803] Found placemark: Outer Mongolia, Mongolia @ <+47.00000000,+103.00000000> +/- 100.00m, region (identifier <+47.00019850,+103.00094600> radius 129.34) <+47.00019850,+103.00094600> radius 129.34m

Warnings

It’s great that we can specify location information with such free syntax. However, with that great power comes great responsibility. Let’s say you have a perfectly normal-sounding US zip code, 38720. That zip code belongs to Duncan, MS. However, when we run it through Core Location, we get this:

Forward Geocoding[7123:f803] Found placemark: 38720 Kankaanpaa, Finland @ <+61.74309935,+22.56331008> +/- 100.00m, region (identifier <+61.74316400,+22.57415800> radius 9380.53) <+61.74316400,+22.57415800> radius 9380.53m

That’s in Finland, folks. Not quite what we were looking for. However, we do get an array of placemarks back - perhaps it’s somewhere in the array, just not the first object?

CLGeocoder *geocoder = [[CLGeocoder alloc] init];

CLGeocodeCompletionHandler completionHandler = 
^(NSArray *placemarks, NSError *error)
{
    for (CLPlacemark *placemark in placemarks)
    {
        NSLog(@"Found placemark: %@", placemark);
    }
};

[geocoder geocodeAddressString:@"38720" completionHandler:completionHandler];
Forward Geocoding[8216:f803] Found placemark: 38720 Kankaanpaa, Finland @ <+61.74309935,+22.56331008> +/- 100.00m, region (identifier <+61.74316400,+22.57415800> radius 9380.53) <+61.74316400,+22.57415800> radius 9380.53m

Forward Geocoding[8216:f803] Found placemark: Duncan, MS  38720, United States @ <+34.11106550,-90.82042082> +/- 100.00m, region (identifier <+34.10980250,-90.82122800> radius 15440.36) <+34.10980250,-90.82122800> radius 15440.36m

Forward Geocoding[8216:f803] Found placemark: 38720 San Andrés y Sauces (Canary Islands), Spain @ <+28.77990090,-17.79729925> +/- 100.00m, region (identifier <+28.78246300,-17.79785150> radius 6970.58) <+28.78246300,-17.79785150> radius 6970.58m

Okay, so it found it after all - it’s the second item in the array, not the first. We can just filter out the non-US results and we’ll be fine. However, why not let Core Location do that for us? Instead, let’s be more specific and use 38720 United States:

Forward Geocoding[7214:f803] Found placemark: Duncan, MS  38720, United States @ <+34.11106550,-90.82042082> +/- 100.00m, region (identifier <+34.10980250,-90.82122800> radius 15440.36) <+34.10980250,-90.82122800> radius 15440.36m

Ahh, there we go. The moral here is to be careful and specify as much information as you have. I learned that one the hard way.

Legacy OS versions

Unfortunately, when you run this code on iOS 4, you get no output. As mentioned above, forward geocoding is an iOS 5 feature. If you need it on iOS versions earlier than 5, there are a few options, including:

  1. Third-party geocoding services
  2. In-storage geocoding database

Conclusion

Where might this be useful? Beyond the obvious uses, like allowing a user to input a zip code or city/state name and having it come back with locations when your server only supports lat/long searching, you can now display city details rather than just “Current Location”.

Go forth and geocode!