Github, the most popular open source repository for open source software, offers a feature that let’s us view repositories by language. In this post, I want to dissect some of the most popular Swift repositories as of June 5th, 2015. So, let’s kick it off with the most starred Swift repository, Alamofire.
Alamofire
Alamofire is an “Elegant HTTP Networking in Swift”, written by Matt Thompson, a well known Objective-C developer responsible for the AFNetworking library. The library takes the built-in iOS networking features and abstracts them away in to a simpler, and more Swift-y API.
Take for example the case of a simple GET request that returns JSON:
Alamofire.request(.GET, "http://httpbin.org/get") .responseString { (_, _, string, _) in println(string) }
It’s the most popular library for Swift to date, so you should probably be using it, right?
Well, maybe… The library makes some common tasks simpler and less verbose, but if you don’t know the basics of networking in Swift (or Objective-C), it’s probably best to get a good understanding of the existing APIs first. After understanding what’s going on under the hood, you can make a more informed decision about whether or not you need the extra layer of abstraction. Alamofire is a big framework, and networking is a huge topic, so I don’t want to get too far in to the details on this library. But, suffice to say if you are working with complex networking requests with lots of back and forth with a web server, and/or working with a complicated authentication process, using Alamofire might reduce some of the repetitive coding tasks. If you are new to iOS development, I would recommend just stick to the APIs provided by Apple for now.
SwiftyJSON
SwiftyJSON is “The better way to deal with JSON data in Swift” according to it’s Github entry. This framework is one of the first I saw when Swift was first released, that combined with the fact that JSON parsing is such a common problem is how it became a top repository, helping to deal with the messiness of Apple’s built-in JSON parser. In particular, the static typing of Swift and optional syntax led to a lot of verbose JSON parsing code, guessing and checking each step of the way for each key and checking every cast. The truth is though, using this library is VERY similar to just using optional chaining and Swift’s normal casting syntax. There is not much benefit here, and from what I’ve seen in production, SwiftyJSON has some performance problems, as a result I’m not sure I would recommend using it right now, except in prototype apps, or during the learning phase.
Take a look at the example they give as the standard approach to parsing JSON, which they describe as “not good”:
let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) if let statusesArray = JSONObject as? [AnyObject], let status = statusesArray[0] as? [String: AnyObject], let user = status["user"] as? [String: AnyObject], let username = user["name"] as? String { // Finally we got the username }
They then present their alternative version of the syntax:
let json = JSON(data: dataFromNetworking) if let userName = json[0]["user"]["name"].string{ //Now you got your value }
There’s a few issues here, first of which is that the simplifications they are showing are partially just taking advantage of language features that would actually work with the regular parser. Second, it seems like their example actually would not work.
Based on the example code shown above, the example JSON they are parsing looks something like this:
{ "statuses": [ { "user": { "name": "Bob" } } ] }
One issue with this sample right off the bat is that they are casting the initial value of the JSON to an array, which would suggest that the root element is an array, which is invalid JSON. The type of the root object in valid JSON is always going to be a key/value. Or equivalently in Swift, a Dictionary of type [String:AnyObject]. Additionally, it’s good practice to actually check if the JSON parsing succeeded or not.
Once we start going through and fixing all the issues with the sample code, assuming we want to explicitly cast everything as they have shown, we end up with something like this:
if let JSONObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? [String:AnyObject], statusesArray = JSONObject["statuses"] as? [[String:AnyObject]] { let status = statusesArray[0] as [String: AnyObject] if let user = status["user"] as? [String: AnyObject], let username = user["name"] as? String { println("username: \(username)") } } else { println("Failed to parse JSON, handle this problem") }
Now, this is in fact pretty verbose, and could be reduced quite a bit, but let’s stop a think about what we’re doing here. In this example, we are trying to download a list of statuses which are owned by users, and they have names. In an actual Swift application, I would expect there to be a model representing these objects. Maybe something like this:
struct User { let name: String } struct Status { let user: User }
Assume we are using SwiftyJSON for a moment, how would we add these records to the model of our app? Maybe with a bit of code like this…
struct User { let name: String } struct Status { let user: User } let parsedJson = JSON(data: data) for (key, val) in parsedJson["statuses"] { if let username = val["user"]["name"].string { let owner = User(name: username) let newStatus = Status(user: owner) } }
This works relatively well, assuming we are just creating objects from a JSON feed rather than synchronizing them. But what if there is a server error and the JSON comes back invalid? For example if there is a server error which changes the JSON to present an “error” key, and it no longer includes “statuses”, this loop simply would not be executed. Failing silently is better than crashing, but it would be nice to check for issues and try again, or adjust something in the app.
Since we need to check for the presence of statuses, and this for loop doesn’t actually do that, we need to check the count of statuses first, which means we need to cast it to an array, and *then* check the count…
if(parsedJson["statuses"].count<1) { println("Oops! An error occurred") }
And that's that! Right?
Well, no...
If the key isn't defined, this count property evaluates to 0, which could just mean there is no new statuses to see. The count really should not be zero, it should be null.. but SwiftyJSON is telling us it's 0. This seems like the kind of thing I really *don't* want a JSON parser to be doing. They really seem to not like the optional syntax in Swift, and instead reinvented it with these type properties. Why not just stick with convention?
Our final code might look something like this:
struct User { let name: String } struct Status { let user: User } let parsedJson = JSON(data: data) for (key, val) in parsedJson["statuses"] { if let username = val["user"]["name"].string { let owner = User(name: username) let newStatus = Status(user: owner) } } if(parsedJson["statuses"].count<1) { println("Oops! An error occurred") } if let err = parsedJson["error"].string { println(err) }
Our code is starting to grow, and this doesn't cover a ton of things we would need in a real-world application, such as updating the model, including more properties, checking for equality, enforcing uniqueness, cascading relationship changes, and a host of other things. Core Data can handle much of this, and it's common practice to implement models as Core Data models, but that still creates a situation where we have to custom implement all kinds of methods for converting the entire model object (such as Status) *back* in to JSON to update the server.
In the Objective-C world there is Mantle, a great library for handling such things. Before that there was RestKit. RestKit however made some ...interesting... design decisions a few years ago in a big update, and haven't ever really recovered since then. Unfortunately I haven't found a good solution for Swift just yet, and trying to work with Mantle proves to be problematic in it's current form, unless you implement all your models in Obj-C, something I'm not sure we all want to do at this stage.
I know this isn't all problems with SwiftyJSON, but they ironically break a lot of Swift conventions in dealing with optional values. SwiftyJSON is really a terrible name, they are very much not Swifty at all. However, the syntax is a little easier on the eyes. Personally, I don't use the library in my projects.
Spring
Spring is "A library to simplify iOS animations in Swift." How does it do this? Let's take a look.
Trying out some sample code I threw together this quick demo UIViewController that adds a blue square to the screen and animates it in, give it a try yourself, it's pretty nifty:
import UIKit import Spring class ViewController: UIViewController { var square = SpringView(frame: CGRectMake(0, 0, 200, 200)) override func viewDidLoad() { super.viewDidLoad() square.center = self.view.center square.backgroundColor = UIColor.blueColor() square.animation = "squeezeDown" square.animate() self.view.addSubview(square) } }
The SpringView seems to basically just be a UIView subclass with the animations added in. I don't know if I really like the idea of having to use their UIView, but I suppose most of the time I just use the basic UIView, and even if I didn't, I could just subclass SpringView instead.
Spring sports quite a few animation types, set as a string. The square.animation = "squeezeDown" here is what's determining the animation to play. The library goes beyond this, and in fact allows simple animations to be created in storyboards. So in theory you could put Spring in your Xcode project, and then pass it off to a designer to set up some nifty animations using this library. Very interesting idea, I would like to hear from someone who has tried to do exactly this.
Quick
Quick is "The Swift (and Objective-C) testing framework."
Really? It's THE testing framework? Let's take a look at how Quick works as opposed to XCTest, or expecta.
In XCTest, you might define an assertion that you're testing against like this:
class JSONSwiftTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testContrivedExample() { let name = "jameson" XCTAssertEqual(name, "jameson", "Name should be \"jameson\"") } }
This is okay, it makes the test basically confirm that name is equal to "jameson". It's simple enough, but there is a common trend/desire among developers to instead express test cases in terms of desired behavior, rather than specifically implementing what the desired behavior causes. Those may sound like the same thing, but take a look at how Quick (due to it's usage of the library Nimble) expresses the same thing like this:
import Quick import Nimble class AQuickTest: QuickSpec { override func spec() { describe("the user") { it("has the name 'Jameson'") { let name = "Jameson" expect(name).to(equal("Jameson")) } } } }
More than anything else, this framework encourages behavioral tests, which is why this example includes more information about our expectations.
Quick also eases some of the pain of asynchronous testing. In XCTest I personally tend to use XCTestAsync, although Xcode 6 does introduce a way to do this using XCTestExpectation. The basic way that works is you can create an expectation object, and then fulfill it when the async operation is complete. It's not a bad approach.
import Quick import Nimble @objc class AsyncExample { var longTaskIsDone = false var timer: NSTimer? func startLongTask() { timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: "taskIsDone", userInfo: nil, repeats: false) } func taskIsDone() { println("task done") longTaskIsDone = true } } class AQuickTest: QuickSpec { override func spec() { describe("the user") { it("has the name 'Jameson'") { let name = "Jameson" expect(name).to(equal("Jameson")) } } describe("Async Example") { describe("its long task") { it("should finish in 5 seconds") { let asyncExample = AsyncExample() asyncExample.startLongTask() expect(asyncExample.longTaskIsDone).toEventually(beTruthy(), timeout: 3, pollInterval: 0.4) } } } } }
In this example we just create an NSTimer that fires in 2 seconds, as a simulated async example. Then in the Async Example test, we can use the .toEventually() method to wait around and keep checking in to the asyncExample.longTaskIsDone property. This is slightly cleaner in that using expectations, because with this method we don't need to change our code to make sure the test is notified of this variable changing. Having an ongoing timer keep checking is great (just be careful not to have it calling methods with side effects!)
Overall Quick seems pretty interesting, the approach is sure to appeal to those in professional environments, or working with an Agile team where specs change fast.
That's it for this time, if you would like to see any of these libraries covered in greater detail be sure to let me know. You can find me on Twitter.