Helpful Mobile Development Libraries: Alamofire and SwiftyJSON

August 8, 2014

Since my first post about ZipGet, other Swift programmers have published some nifty libraries, namely Alamofire and SwiftyJSON. Alamofire comes from the makers of AFNetworking, a ubiquitous and handy networking library for iOS. Unlike AFNetworking, though, Alamofire is written completely in Swift. SwiftyJSON does not boast as illustrious a pedigree as Alamofire, but it greatly simplified my code. SwiftyJSON allows me to explore JSON returned from web services almost as easily as JavaScript does.

SwiftyJSON

All the interesting changes take place in the ZipCodeFinder class. Before SwiftyJSON, the meaty part of the code looked as follows:

let json = response as [String:AnyObject] let postalCodes: AnyObject? = json["postalCodes"] let postalCodesArr = postalCodes as [[String:AnyObject]] if !postalCodesArr.isEmpty {     let first = postalCodesArr[0]     if let postalCode: AnyObject = first["postalCode"] {        // Now I've got my postal code     } }

response is the JSON from GeoNames as returned to me by AFNetworking: an AnyObject? masking a nest of NSDictionary and NSArray instances. All those if statements and the type casting obscure what should be a straightforward dive into the JSON. Here is what the same chunk of functionality looks like now:

let json = JSONValue(data as NSData) if let postalCode = json["postalCodes"][0]["postalCode"].string {     // Hey, look! A postal code. }

data is the JSON from GeoNames as presented by Alamofire. JSONValue() is the initializer for an enumeration from SwiftyJSON that takes an instance of NSData or AnyObject. The string() method is one of a set of methods named after the various types one might be retrieving from a JSON object and each method returns an optional of its namesake type. What’s interesting to me is the path of keys and indices. Each successive subscript either delves farther into the JSON structure or adds an error message so that finding where you went wrong should be a trivial task. I think it’s a rather well-thought-out approach to JSON in Swift.

Alamofire

ZipGet(seriously, there has to be a better name for this thing) makes only simple requests, so using Alamofire over AFNetworking doesn’t bring as much of an improvement as the addition of SwiftyJSON does. However, it does feel like a more natural interface. Here’s the old code:

// Property declaration in the ZipCodeFinder class let manager: AFHTTPRequestOperationManager // Initialization in the ZipCodeFinder init method self.manager = AFHTTPRequestOperationManager() // Usage manager.GET(url,     parameters: parameters,     success: { (op: AFHTTPRequestOperation!, response: AnyObject!)         in         // Success handling code     },     failure: { (op: AFHTTPRequestOperation!, error: NSError!)         in         // Failure handling code     })

It’s actually not that bad. I don’t really have any complaints regarding AFNetworking’s usability after being bridged over from Objective-C. Here’s a look at the new version:

Alamofire.request(.GET, url, parameters: parameters).response { request, response, data, error in     // Completion handling code }

I like that I am no longer instantiating a manager object; request() is a static method on the Alamofire struct. In my actual code, I don’t care about the request, response or error parameters, so I have replaced them with underscores. For anyone unfamiliar, .GET as the first parameter to request() is an example of Swift’s type inference. GET is actually a case of Alamofire’s Method enumeration, which one might normally write as Method.GET, but because Swift knows the parameter is of type Method, we can just write .GET. Additionally, the framework is structured to encourage, or at least allow, the use of Swift’s trailing closure syntax, which I find pleasing to use.

I have a slight issue with the consolidation of the success and failure callbacks into one completion callback. At the moment, documentation is sparse for Alamofire so I am not certain what error(s) the error parameter is meant to encapsulate. My solution so far is to check for GeoNames’ error message, check for the values I actually want, and call the whole thing a failure if neither of those checks yields information. As before, check out the repository on GitHub if you’re so inclined.