Skip to content

Instantly share code, notes, and snippets.

@chenr2
Last active August 29, 2015 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chenr2/66f15d3296d8f8f60f13 to your computer and use it in GitHub Desktop.
Save chenr2/66f15d3296d8f8f60f13 to your computer and use it in GitHub Desktop.
Swift: Demonstrating the Network Starter Kit for the Columbia iOS meetup

Demo of the Network Starter Kit

See Network Starter Kit.

This is the script for Tuesday's Columbia iOS meetup.

Some basics

Object Contains
URL http://www.google.com?q=foo
Request verb, timeout, url, headers, body
Task resume, cancel
Session like a browser session

GET Request

Problem statement.

Just to get this output: label:: Agar.io

we have all this code:

let url = NSURL(string: "https://itunes.apple.com/us/rss/topfreeapplications/limit=10/json")
let request = NSMutableURLRequest(URL: url!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
    (data, response, error) -> Void in
    println("data:: \(data)")
    println("response:: \(response)")
    println("error:: \(error)")
    
    // data: is still totally unreadable, and needs to be converted to JSON

    var jsonError: NSError?
    if let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as? [String: AnyObject] {
        
        println("json:: \(json)")
        
        if let feed = json["feed"] as? NSDictionary {
            if let entry = feed["entry"] as? NSArray {
                if let appObject = entry[0] as? NSDictionary {
                    if let imName = appObject["im:name"] as? NSDictionary {
                        if let label = imName["label"] as? String {
                            println("label:: \(label)")
                        }
                    }
                }
            }
        }
    }

}
task.resume()

Pain points:

  • Lots of boilerplate just to setup a network request. Imagine if you have a dozen API endpoints.
  • NSJSONSerialization is kind of yucky. We still need to protect against jsonError bombing.
  • Check out the if-let pyramid of doom
  • error is triggered if there was a physical network problem, like bad wifi. You still need to check for status codes (404, 500) and for malformed JSON.

GET using AlamoFire and SwiftyJSON

Syntax is tighter and easier to read.

let url = "https://itunes.apple.com/us/rss/topfreeapplications/limit=10/json"
request(.GET, url)
    .validate()
    .responseJSON { (_, _, data, error) in
        if error == nil {
            let json = JSON(data!)
            let label = json["feed"]["entry"][0]["im:name"]["label"].stringValue
            println("label:: \(label)")
        }
    }

Thanks to validate(), the error is triggered not only when there's a bad physical connection, but also when you get a bad response.

What about POST?

This is what a vanilla POST looks like. Not only do you need to convert your dictionary to JSON, you also need to set application/json headers.

let url = NSURL(string: "http://httpbin.org/post")
let params = [ "foo" : "bar" ]
var err: NSError?

let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error -> Void in
    println("data:: \(data)")
    println("response:: \(response)")
    println("error:: \(error)")
    
    var jsonError: NSError?
    if let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as? [String: AnyObject] {
        
        println("json:: \(json)")
    }
    
}
task.resume()

A better way to POST

This does the same thing, but better

let params = [ "foo" : "bar" ]
request(.POST, "http://httpbin.org/post", parameters: params, encoding: .JSON)
    .validate()
    .responseJSON { (_, _, data, error) in
        if error == nil {
            let json = JSON(data!)
            println("json:: \(json)")
        }
    }

How about downloading images?

This is the normal way:

let url = NSURL(string: "http://tsumtsumdisney.com/wp-content/uploads/2014/10/Snow-White-and-the-Seven-Dwarfs-Tsum-Tsum-Set-of-8-0-0.jpg")
let request = NSURLRequest(URL: url!)
let mainQueue = NSOperationQueue.mainQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue) {
    (response, data, error) -> Void in
    if error == nil {
        let image = UIImage(data: data)
        dispatch_async(dispatch_get_main_queue()) {
            self.imageView.image = image
        }
    }
}

While this works, you still have to address 3 problems:

  • Image caching. You need to respect your customer's data usage.
  • Cache validation. You need to inspect the downloaded image and make sure you don't cache a corrupt image.
  • UIImage thread safety. Now that you're caching, multiple cells will call UIImage(data: data) simultaneously and crash intermittently.

(A fourth problem with the code above: NSURLConnection is deprecated. Use NSURLSession instead because it supports HTTP/2 automatically.)

A better way to download images

This is how to download an image using the Network Starter Kit. It also fixes the 3 problems mentioned above.

request(.GET, "http://tsumtsumdisney.com/wp-content/uploads/2014/10/Snow-White-and-the-Seven-Dwarfs-Tsum-Tsum-Set-of-8-0-0.jpg")
    .responseImage() { (request, _, image, error) in
        if error == nil && image != nil {
            self.imageView.image = image
        }
    }

You see AlamoFire at work.

But there's also this .responseImage() that gives you back an image on a silver platter. This is thanks to an Image Response Serializer from a RW tutorial.

(Note to self: when doing the demo on a blank project, don't forget to follow the integration steps. You'll need to at least include the 2 extensions at the bottom of the Router. And the caching lines in AppDelegate)

What if you have a lot of API calls?

Go over the router

Multipart

Go over the multipart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment