See Network Starter Kit.
This is the script for Tuesday's Columbia iOS meetup.
Object | Contains |
---|---|
URL | http://www.google.com?q=foo |
Request | verb, timeout, url, headers, body |
Task | resume, cancel |
Session | like a browser session |
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 againstjsonError
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.
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.
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()
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)")
}
}
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 callUIImage(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.)
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 extension
s at the bottom of the Router. And the caching lines in AppDelegate
)
Go over the router
Go over the multipart