Showing posts with label Swift. Show all posts
Showing posts with label Swift. Show all posts

Monday, June 13, 2016

Parsing complex JSON data in Swift 2!

This tutorial will show you how to parse a downloaded, somewhat complex, JSON object in Swift 2.2.

I have created 2 previous tutorials related to this, which get into more details on how to download and parse basic JSON data, and how to return it using completion handlers.
This tutorial is specifically to show you how to parse complex data, and traverse a JSON object tree.
This tutorial assumes you already know how to get the data downloaded, and understand how JSON parsing works.

Project setup

Since this project is all about parsing, I will not be making a UI or any other changes to a basic application.
This application starts from a brand new "Single view application" template of Xcode.

All the code created here will be used in the viewDidAppear() function of the default ViewController.swift class.

Sample JSON data

This tutorial will be using a JSON data I copied from an adobe JSON tutorial, and placed it on a new file so it is easier to download.

This is the url we'll be using: https://api.myjson.com/bins/x4l8
In case the url is down by the time you read this tutorial, here's the content of the entire file/url:
{

    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    "batters": {
        "batter": [
            {
                "id": "1001",
                "type": "Regular"
            },
            {
                "id": "1002",
                "type": "Chocolate"
            },
            {
                "id": "1003",
                "type": "Blueberry"
            },
            {
                "id": "1004",
                "type": "Devil's Food"
            }
        ]
    },
    "topping": [
        {
            "id": "5001",
            "type": "None"
        },
        {
            "id": "5002",
            "type": "Glazed"
        },
        {
            "id": "5005",
            "type": "Sugar"
        },
        {
            "id": "5007",
            "type": "Powdered Sugar"
        },
        {
            "id": "5006",
            "type": "Chocolate with Sprinkles"
        },
        {
            "id": "5003",
            "type": "Chocolate"
        },
        {
            "id": "5004",
            "type": "Maple"
        }
    ]

}

Download data

With all of the setup elements out of the way, we will turn to Xcode and start working on this.
Let's begin by downloading the data. Since this is the same code we used and explained in the first 2 tutorials, I will just copy the code block and assume you understand what I'm doing.
import UIKit
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // this is sample data I copied from Adobe.github.io
        // adobe.github.io/Spry/samples/data_region/JSONDataSetSample.html#Example2
        
        let jsonUrlAsString = "https://api.myjson.com/bins/x4l8"
        
        // make the GET call asynchrounously
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration();
        let headers: [NSObject : AnyObject] = ["Accept":"application/json"];
        configuration.HTTPAdditionalHeaders = headers;
        let session = NSURLSession(configuration: configuration)
        let dataTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (data, response, error) in
            
            if(error == nil)
            {
            }
            else
            {
                print("Error downloading JSON data. Error = \(error)")
            }
        }
        dataTask.resume()
    }
}
And with the data downloaded, we need to begin our parsing process.
But first...

Understand JSON

Note: if you already understand what JSONs are, you may want to skip this section.
JSON strings are super easy. They are based on two concepts: JSON Objects and JSON Arrays, and key value pairs.

Key value pairs

These are 2 words that are separated with a colon (:), and each key value pair is separated with a comma (,). They are presented as this:

"key1":"value1", "key2":"value2","myKey":"my value","monster":"Cookie Monster", "myAge":32

Keys can have spaces, but they usually have issues on some programming languages, so that makes most keys to always be without spaces.
Values can be whatever you want. If they're Strings then they have quotes ("") around them, while integers and doubles don't have them. However, sometimes the output from the log from an IDE might place quotes around everything.

JSON Objects and JSON Arrays

By default a single JSON string contains a single JSON Object, but the most important part to remember about JSON Objects and JSON Arrays is this:

JSON Object are represented as {} (curly braces)
JSON Arrays are (usually) represented as [] (square brackets)

Note: XCode usually outputs JSON Arrays as () (parenthesis)

So with that, we can create a JSON Object like this:
{
 "key1": "value1",
 "monster": "Cookie Monster"
}
This is a JSON Object with 2 key values (we call that fields now)
Before we talk about JSON Arrays, you need to see what we can do now: we can do a key value pair with a key a JSON Object, like this:
{
 "key1": "value1",
 "monster": "Cookie Monster",
 "carObject": {
  "brand": "Ford",
  "model": "Model A"
 },
 "computerObject": {
  "brand": "Apple",
  "model": "Macbook pro"
 }
}
In here, we not have a key of carObject with a value of a new JSON Object. This new JSON Object has it's own set of key values. Same thing with computerObject.

JSON Arrays are basically what you would expect them to be: an array of JSON Objects. The only thing to remember is that JSON Arrays wrap JSON Objects, so they are represented before a curly brace.
Here's an example of a JSON Object with a JSON Array:
{
 "key1": "value1",
 "monster": "Cookie Monster",
 "carObject": [{
  "brand": "Ford",
  "model": "Model A"
 }, {
  "brand": "Chevy",
  "model": "Camaro"
 }]
}
So now inside the key of carObject we now have a JSON Array of JSON Objects. In this case we have 2 JSON Objects, each of them with their own key value pairs.

Actual Parsing process

Just like in the previous tutorials, we first need to convert our downloaded data into a JSON object, and we will do this using NSJSONSerialization.JSONObjectWithData function from the Foundation framework.
Then, instead copying one line at the time, I'm gonna copy the whole thing I did, and tell you later what it means.
Here's my entire class code:
import UIKit
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // this is sample data I copied from Adobe.github.io
        // adobe.github.io/Spry/samples/data_region/JSONDataSetSample.html#Example2
        
        let jsonUrlAsString = "https://api.myjson.com/bins/x4l8"
        
        // 1. make the GET call asynchrounously
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration();
        let headers: [NSObject : AnyObject] = ["Accept":"application/json"];
        configuration.HTTPAdditionalHeaders = headers;
        let session = NSURLSession(configuration: configuration)
        let dataTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (data, response, error) in
            
            if(error == nil)
            {
                do
                {
                    // 2. convert downloaded NSData into JSONObject
                    let entireJson = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments);
                    print("entireJson = \(entireJson)")
                    
                    // 3. parse root elements
                    let rootId = entireJson["id"] as? String
                    let rootType = entireJson["type"] as? String
                    let rootName = entireJson["name"] as? String
                    let rootPpu = entireJson["ppu"] as? Double          // no idea what ppu is or means
                   
                    //output result to console
                    print("rootId = \(rootId!)")
                    print("rootType = \(rootType!)")
                    print("rootName = \(rootName!)")
                    print("rootPpu = \(rootPpu!)")
                    
                    /*
                     * Batters section of the JSON
                     */
                    // 4. parse the 'batters' element
                    let battersDictionary = entireJson["batters"] as? NSDictionary
                    print("battersDictionary = \(battersDictionary!)")
                    
                    // 5. parse 'batter' element, expect an array
                    let batterArray = battersDictionary!["batter"] as? [NSDictionary]
                    print("batterArray = \(batterArray!)")
                    
                    // 6. iterate the batter array of dictionaries
                    for eachBatter in batterArray!
                    {
                        print("eachBatter = \(eachBatter)")
                        let eachBatterId = eachBatter["id"] as? String
                        let eachBatterType = eachBatter["type"] as? String
                        
                        //output result to console
                        print("eachBatterId = \(eachBatterId!)")
                        print("eachBatterType = \(eachBatterType!)")
                    }

                    
                    /*
                     * Toppings section of the JSON
                     */
                    let toppingArray = entireJson["topping"] as? [NSDictionary]
                    print("toppingArray = \(toppingArray!)")
                    
                    // interate the topping array of dictionaries
                    for eachTopping in toppingArray!
                    {
                        print("eachTopping = \(eachTopping)")
                        let eachToppingId = eachTopping["id"] as? String
                        let eachToppingType = eachTopping["type"] as? String
                        
                        //output result to console
                        print("eachToppingId = \(eachToppingId!)")
                        print("eachToppingType = \(eachToppingType!)")
                    }
                    
                }
                catch
                {
                    print("Error serializing JSON data")
                }
            }
            else
            {
                print("Error downloading JSON data. Error = \(error)")
            }
        }
        dataTask.resume()
    }
}
So....lets go by the numbers in the comments.

1 and 2. We make the GET call, and convert the NSData to a JSON Object. I covered that section extensively in a previous tutorial, so I won't get into it here.

3. We parse the root elements. These are elements that are at the root level, and not inside another JSON Object or JSON Array. There are only 4 of them in the sample JSON:
{

    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    // other stuff
} 
Notice the format used is: ["key"] as? String.
We use the key that comes from the JSON, the as? keyword which will allow nil values, and String is what we're expecting to get.
In one of the elements, ppu, we use as? Double, because we expect a Double type.

4. We move to the batters section:
{

    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    "batters": {
        "batter": [
    // more later
}
The batters section is an dictionary (AKA as map, or key-value pair), and we're actually expecting then an NSDictionary, we use this format:
["key"] as? NSDictionary

5. Inside of batters, we have objects that are "batter":[ ]. Remember that the [ ] is an array, and since we're using JSON format, our elements inside the array will always be a key-value pair.
This means, we have an array of dictionaries. In Apple-lingo, we call it an NSArray of NSDictionaries.
So we use this format for it:
["key"] as? [NSDictionary]

Since we're the 'batters' dictionary already, we do it like this:
myNextLevelJSONDictionary["key"] as? [NSDictionary]
This covers only this portion of the JSON:
"batters": {
    "batter": [

Note. Since this is an array of NSDictionaries, we could've also used this key:
["key"] as? [String:AnyObject]
Both formats work, although NSDictionary is preferred!

6. The array of dictionaries gave us...well, an array, and because of that we can iterate through it to get each element inside the array, individually.
In here I just used a regular for-each (or fast enumeration) loop.
Then, inside the loop I can get the values of each 'batter':
"batter": [
    {
    "id": "1001",
    "type": "Regular"
    },
With this, like format, like before:
eachBatter["key"] as? String

Then you repeat the recipe (no pun intended), and do it for Toppings.

I hope that helps!
Eduardo.

Saturday, June 11, 2016

Completion handlers for NSURLSessionDataTask. Returning downloaded data on iOS

This tutorial picks up exactly where I left my previous iOS tutorial of "Downloading and parsing JSON data in Swift 2.2", so you may want to at least read over that one to see where we're starting from, and how we got here.

Refactor code

The very first step is refactoring our code, primarily to make more sense of how a real application would use completion handlers to pass downloaded data around.

We'll begin by creating a new Swift class called "Utilities.swift" which will inherit from NSObject:
import Foundation
class Utilities : NSObject
{
    
}
New file added, and the project structure

We will then create a new static function (didn't we use to call them method in obj-c?) called getJsonData, which will take 1 parameter, of type String, and returns Void.
The Utilities class should look like this now:
import Foundation
class Utilities : NSObject
{
    static func getJsonData(jsonUrlAsString:String) -> Void
    {
        
    }
}
We then go back to our ViewController class we were using on the first tutorial, and cut everything from the network configuration, to the resume() call. We will paste this inside of our getJsonData inside of our Utilities class.

The Utilities class should be complaining about the UILabels we set in the previous tutorial.
We will need the code for this UILabels later, but since its easy to create we will just delete it from Utilities.

At this point, our Utilities class should look like this:
import Foundation
class Utilities : NSObject
{
    static func getJsonData(jsonUrlAsString:String) -> Void
    {
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)
        
        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                }
                catch
                {
                    
                }
            }
            else
            {
                print("Error downloading data. Error = \(error)")
            }
        }
        
        // actually execute the task
        downloadTask.resume()
    }
}

On our ViewController we want to call the function getJsonData from the Utilities class, so we do this passing the url as a String.
At this point, the ViewController class should look like this:
import UIKit
class ViewController: UIViewController
{

    @IBOutlet weak var labelName: UILabel!
    @IBOutlet weak var labelAge: UILabel!
    @IBOutlet weak var labelGender: UILabel!
    @IBOutlet weak var labelCountry: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        Utilities.getJsonData(jsonUrlAsString)        
    }
}
So now, if we were to run our application, we should not have any compiling errors, and we should see the output on the console displaying something like this:
dataReceived = Optional()
dataDownloadedAsJson = {
    age = 32;
    country = USA;
    gender = male;
    name = "Eduardo Flores";
}
nameRead = Eduardo Flores
countryRead = USA
genderRead = male
ageRead = 32
sdad

If the code transplant so far does the same thing as it does to me, then we're ready to move on.
Everything should work, except...well the app UI.
Before we fix that we need to create an object to return.

Create return Object
I could pass a 4 variables back, but that'll be horrible and not what you'll do on a regular basis. (you don't do that, right?)
So let's create a new swift class and let's call it "Person.swift"
In here, we'll create 4 variables: name, age, gender and country.
The whole object should look like this:
import Foundation
class Person: NSObject
{
    var name : String?
    var age : Int?
    var gender : String?
    var country : String?
}
Now that we have the object data, let's add the Person object to the getJsonData function from the Utilities class. Now the code inside getJsonData looks like this:
do
static func getJsonData(jsonUrlAsString:String) -> Void
{
    let person = Person()

    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
    configuration.HTTPAdditionalHeaders = headers
    let session = NSURLSession(configuration: configuration)
    
    let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        if (error == nil)
        {
            print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
            
            let nameRead = dataDownloadedAsJson["name"] as? String
            let countryRead = dataDownloadedAsJson["country"] as? String
            let genderRead = dataDownloadedAsJson["gender"] as? String
            let ageRead = dataDownloadedAsJson["age"] as? Int
            
            print("nameRead = \(nameRead!)")
            print("countryRead = \(countryRead!)")
            print("genderRead = \(genderRead!)")
            print("ageRead = \(ageRead!)")
            
            person.name = nameRead!
            person.age = ageRead!
            person.gender = genderRead!
            person.country = countryRead!
        }
        catch
        {
            
        }
        }
        else
        {
            print("Error downloading data. Error = \(error)")
        }
    }
    
    // actually execute the task
    downloadTask.resume()
}
And now we need to return the Person object.
This is not as simple as you expect...

Return data as return type

The issue we have is that we have data in one class, but we want to use it on another class. This is a fairly common affair in programming, and the initial approach would be to return Person instead of Void from the getJsonData function.

So let's try it!

In Utilities, change getJsonData the function to this:
static func getJsonData(jsonUrlAsString:String) -> Person
{
    let person = Person()

    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
    configuration.HTTPAdditionalHeaders = headers
    let session = NSURLSession(configuration: configuration)
    
    let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        if (error == nil)
        {
            print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
            
            let nameRead = dataDownloadedAsJson["name"] as? String
            let countryRead = dataDownloadedAsJson["country"] as? String
            let genderRead = dataDownloadedAsJson["gender"] as? String
            let ageRead = dataDownloadedAsJson["age"] as? Int
            
            print("nameRead = \(nameRead!)")
            print("countryRead = \(countryRead!)")
            print("genderRead = \(genderRead!)")
            print("ageRead = \(ageRead!)")
            
            person.name = nameRead!
            person.age = ageRead!
            person.gender = genderRead!
            person.country = countryRead!
        }
        catch
        {
            
        }
        }
        else
        {
            print("Error downloading data. Error = \(error)")
        }
    }
    
    // actually execute the task
    downloadTask.resume()
    return person
}
and in the ViewController, change the way you call the getJsonData function, to this (and applying the result to the UI):
override func viewDidLoad() {
    super.viewDidLoad()
    
    let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
    let person = Utilities.getJsonData(jsonUrlAsString)
    
    print("person.name = \(person.name!)")
    labelName.text = person.name!
}
If you run this, you'll be getting a runtime error of something like this:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb) 

Why are we getting this?
Well, the issue is that the getJsonData is downloading data on a different thread, and while this is great, it also means that the return statement of the getJsonData function is getting called before the data finishes downloading.
So, because of this, you're returning an empty Person object, with just nil values. Therefore person.name is nil

In other words, we're returning nothing because we're reaching our return statement before we ever even make our network call.
And that's not good...

Returning data with completion handler

Since we cannot return the data as a good ol' return type, we need a different way to return the data, after we know the download has completed....whenever that is.

For this we use Completion Handlers, also known as Callbacks in other languages.

I won't get into details on this, so if you want to learn more about Completion Handlers there are tons of online resources for that.
In short, they are a parameter sent from whoever wants to be notified of something happening on a different class or function.
In this tutorial, I'm just teaching you how to use them with a specific example to achieve something.

1. The first thing we'll do is modify the getJsonData function to accept a completion handler parameter.
Change the getJsonData function declaration from this:
static func getJsonData(jsonUrlAsString:String) -> Person
to this:
static func getJsonData(jsonUrlAsString:String, completionHandlerPerson:(responsePerson:Person?, errorPerson:NSError?) -> ())

In here, we're passing a new parameter called completionHandlerPerson, which will return 2 elements: a Person object response (named responsePerson), and a NSError object (named errorPerson)
We're also just returning () or Void now, since the return will be handled by the completionHandlerPerson.
Since our completionHandlerPerson has a Person object and a NSError object, we must always use it retuning both elements. This is handy because if we do get data downloaded, the NSError element will be nil, which is easy to check.

2. With the function declaration modified, we now need to return our data with our completionHandlerPerson element.
Our entire getJsonData function will now look like this:
static func getJsonData(jsonUrlAsString:String, completionHandlerPerson:(responsePerson:Person?, errorPerson:NSError?) -> ())
{
    let person = Person()

    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
    configuration.HTTPAdditionalHeaders = headers
    let session = NSURLSession(configuration: configuration)
    
    let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        if (error == nil)
        {
            print("dataReceived = \(dataReceived)")
            do
            {
                let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                
                let nameRead = dataDownloadedAsJson["name"] as? String
                let countryRead = dataDownloadedAsJson["country"] as? String
                let genderRead = dataDownloadedAsJson["gender"] as? String
                let ageRead = dataDownloadedAsJson["age"] as? Int
                
                print("nameRead = \(nameRead!)")
                print("countryRead = \(countryRead!)")
                print("genderRead = \(genderRead!)")
                print("ageRead = \(ageRead!)")
                
                person.name = nameRead!
                person.age = ageRead!
                person.gender = genderRead!
                person.country = countryRead!
                
                completionHandlerPerson(responsePerson: person, errorPerson: nil)
            }
            catch
            {
                
            }
        }
            else
            {
                print("Error downloading data. Error = \(error)")
                completionHandlerPerson(responsePerson: nil, errorPerson: error)

            }
        }
    
    // actually execute the task
    downloadTask.resume()
}
Notice how we call the completionHandlerPerson twice: once for when the response is valid, and once for when the error happens.
We should also return a third version for when the parser fails, but that'll be your homework.

And with that, the Utilities.getJsonData function is complete.
We need to modify the caller class of ViewController.

In ViewController, you need to change this:
let person = Utilities.getJsonData(jsonUrlAsString)
for this:
Utilities.getJsonData(jsonUrlAsString,
                              completionHandlerPerson: {(responsePerson, errorPerson) -> Void in
                          })
so now we're passing our custom completion handler as a parameter to Utilities, and whenever the download thread finishes, we will be notified to do whatever want to do.

With that, all we're missing is placing the UI elements inside the caller completion handler, and we'll be done.

Here's the final ViewController class:
import UIKit
class ViewController: UIViewController
{

    @IBOutlet weak var labelName: UILabel!
    @IBOutlet weak var labelAge: UILabel!
    @IBOutlet weak var labelGender: UILabel!
    @IBOutlet weak var labelCountry: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        Utilities.getJsonData(jsonUrlAsString,
                              completionHandlerPerson: {(responsePerson, errorPerson) -> Void in
                                if (errorPerson == nil)
                                {
                                    // don't forget to update the UI in the main thread!
                                    dispatch_async(dispatch_get_main_queue()) { () -> Void in
                                        self.labelName.text = responsePerson!.name!
                                        self.labelAge.text = String(responsePerson!.age!)
                                        self.labelGender.text = responsePerson!.gender!
                                        self.labelCountry.text = responsePerson!.country!
                                    }
                                }
                                
                            })
    }
}

and here's the final Utilities class:
import Foundation
class Utilities : NSObject
{
    static func getJsonData(jsonUrlAsString:String, completionHandlerPerson:(responsePerson:Person?, errorPerson:NSError?) -> ())
    {
        let person = Person()

        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)
        
        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                    
                    person.name = nameRead!
                    person.age = ageRead!
                    person.gender = genderRead!
                    person.country = countryRead!
                    
                    completionHandlerPerson(responsePerson: person, errorPerson: nil)
                }
                catch
                {
                    
                }
            }
                else
                {
                    print("Error downloading data. Error = \(error)")
                    completionHandlerPerson(responsePerson: nil, errorPerson: error)

                }
            }
        
        // actually execute the task
        downloadTask.resume()
    }
}

And that should be all!
Your app should now be ready to download data in some class, and consume it on another one.

Eduardo

(it's midnight so let me know if I missed something, or something doesn't make sense)

Downloading and parsing JSON data in Swift 2.2

I've primarily moved to the Android world, but I had a side gig that required me to get a jump start on Swift. Last time I touched Swift was with Swift 1.1, and things have changed a bit.

In this tutorial I'm going to show you how to download data from a JSON file stored somewhere on the web, then we're going to parse it, and finally we're going to show it in the UI of the app.

For this tutorial we're going to use NSURLSession and NSURLSessionDataTask to download the data, and we'll use the regular class NSJSONSerialization to parse the downloaded JSON data.
All 3 of these classes are part of the UIKit of iOS, and they are not third-party libraries.

Enough talk, let's get to work.
Note: this was done on June 2016, using Xcode 7.3.1 and Swift 2.2.

Sample JSON

We'll begin with a super simple JSON file that I have stored here, and in case the url is down by the time you read this, here's a copy of it's content:
{

    "name": "Eduardo Flores",
    "age": 32,
    "gender": "male",
    "country": "USA"

}
As you can see, this JSON is super simple. It only contains 1 JSON object (the root object), and 4 key/value pairs, where 3 of these values are Strings and 1 is an Int.

Download the data

For this project I'm going to assume that you know how to create an iOS project in Xcode, so I'll skip that part.
My project is a single view project with nothing on it.

We first need to define the url where our JSON file is, so we'll do that this way:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
    }
}
After this, we need to setup a configuration we're going to use.
While headers won't be really needed for this simple JSON, I will add them to this tutorial so you know where to place them. Headers go as a dictionary of key/value pairs.
Here's what my configuration, making a GET request, with headers looks like:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)
    }
}
If we had additional headers, like an authentication token or something else, that would look like this:
let headers: [NSObject : AnyObject] = ["Accept":"application/json","Auth":"token"]
So, with the configuration set, and the session variable created, we now need to create the NSURLSessionDataTask to actually make the network download.
For this we will use the NSURLSessionDataTask initializer that takes a NSURL, and a completion handler, like this:
session.dataTaskWithURL(NSURL:url>, completionHandler: <(NSData?, NSURLResponse?, NSError?) -> Void)
Since we want to use this, we need to assign this line to a variable.
The variable, with the completion handler using real variables, would then look like this:
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
}
And in order to actually make the network call, we need to call the .resume() method of our downloadTask variable.
All together now, this looks like this:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)

        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        }

        // actually execute the task
        downloadTask.resume()
    }
}
Yay, you've made a GET request. Woohoo!
But nothing visible has happened yet...

Check the downloaded data

In our completion handler of session.dataTaskWithURL we have 3 elements: a NSData object, a NSURLResponse object, and an NSError object.
If something went wrong during the download of the data, our NSError object will have an error, and therefore it won't be nil. If this happens, the NSData and NSURLResponse objects will be nil.
In other words, if the NSError object is nil then the NSData and NSURLResponse objects have data, and if the NSError object is not nil, then the NSData and NSURLResponse objects will be nil.
So, let's check for errors.
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
    if (error == nil)
    {
        print("dataReceived = \(dataReceived)")
    }
    else
    {
        print("Error downloading data. Error = \(error)")
    }
}
This is now inside the downloadTask variable, and all I'm doing here is checking to see if the NSError object is nil. If the NSError object is nil, then I print the downloaded data to the console.
Otherwise it means that the NSError is not nil, and something went wrong somewhere.
Also, since the NSError object is nil, the dataReceived variable (coming from the completion handler of session.dataTaskWithURL) should contain data.

If you run the app now, you should have something like this in the console:
dataReceived = Optional(<os_dispatch_data: buf="0x7fc2d14453a0" data="" leaf="" size="66," x7fc2d1604cd0="">)
This is ugly and unusable, but at least it shows you we're getting data back!

Convert the downloaded NSData to JSON object

Assuming our data downloads correctly, and therefore there are no errors at this point, we need to convert the dataReceived into a readable JSONObject. For this, we'll use the NSJSONSerialization class, like this:
NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
However, this may throw an exception, as most serializers do, and we want to place the result of the Serialization inside of a variable. (note: there are several options inside the NSJSONReadingOptions class if you want to read into this)

So, adding the exception catcher, in true Java try/catch fashion, we do this now:
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
    if (error == nil)
    {
        print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
        }
        catch
        {
            
        }
    }
    else
    {
        print("Error downloading data. Error = \(error)")
    }
}

// actually execute the task
downloadTask.resume()
And now, the output should be something much friendlier, like this:
dataReceived = Optional()
dataDownloadedAsJson = {
    age = 32;
    country = USA;
    gender = male;
    name = "Eduardo Flores";
}
Yay, we have our JSON file, with readable data in the console!

Parsing the JSON data

Now that we have our JSON data available, we need to create Swift variables so we can pass the data around our application. We begin this process by parsing the entire JSON file into small variables for whatever elements we want.

The first thing we're going to do is get the name key of our JSON object. Since this JSON file is super simple, this is a 1 liner, like this:
let nameRead = dataDownloadedAsJson["name"] as? String
print("nameRead = \(nameRead!)")
And when you run it, you should have this output:
name = Eduardo Flores
So, what does this do?
This is actually fairly simple.
1. We have all of our serialized JSON data in a variable called dataDownloadedAsJson
2. Inside our JSON object, we're looking for the key of name
3. We believe, or expect, the value of our key name to be a String object. We could've used the as! keyword (with the exclamation point) instead of as? (with the question mark), but it is preferred to use the question mark version, which allows us to receive nil values. The as! keyword is expecting a String value, while the as? allows String AND nil values. (this is called Optionals, in case you want to read more about it)
4. String would be expected object type
5. We assign the result to this to a new variable called nameRead
6. And when we display it to the console we use the nameRead! with the exclamation point to unwrap the object into a String value

With that, we create variables for all of the key/values from our JSON. Note that you don't need to parse every single element in your JSON and you could just parse the elements you need.

And with that, our entire application looks like this now:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)

        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                }
                catch
                {
                    
                }
            }
            else
            {
                print("Error downloading data. Error = \(error)")
            }
        }
        
        // actually execute the task
        downloadTask.resume()
    }
}


Display data in UI

With the data downloaded and parsed, we now need to display our data in our UI.
For this I've created 4 UI elements in the storyboard, which I've wired up as IBOutlet in my ViewController file.

Control + Click to create connections

So now we could just try the regular self.something command we use to set elements, right?
Let's do it and see what happens. Here's the code of the downloadTask with the new code to set the UI elements:
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
    if (error == nil)
    {
        print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
            
            let nameRead = dataDownloadedAsJson["name"] as? String
            let countryRead = dataDownloadedAsJson["country"] as? String
            let genderRead = dataDownloadedAsJson["gender"] as? String
            let ageRead = dataDownloadedAsJson["age"] as? Int
            
            print("nameRead = \(nameRead!)")
            print("countryRead = \(countryRead!)")
            print("genderRead = \(genderRead!)")
            print("ageRead = \(ageRead!)")
            
            // set UI elements
            self.labelName.text = nameRead!
            self.labelAge.text = String(ageRead!)
            self.labelGender.text = genderRead!
            self.labelCountry.text = countryRead!
        }
        catch
        {
            
        }
    }
    else
    {
        print("Error downloading data. Error = \(error)")
    }
}
And run the app, and you'll get something like this:
Console output, and Simulator running
Your app runs, the console displays the correct output, but your app in the simulator never shows the correct values.
How is this possible, since we clearly have them in the console?

While we never explicitly requested this, the NSURLSessionDataTask class runs on a separate thread, which IS NOT THE UI THREAD.
This means that all of this code will run on the background, and someday, in the distant future, your UI will catch up and update with the code you run on the background thread.
This is a great feature of NSURLSessionDataTask because it allows us to make multiple network calls without locking up the UI for the user, but you need to be aware of it, and need to learn how to handle it properly (not just waiting forever for the UI to update).

So how do we solve this?
We call Apple's friendly (and C language looking) Grand Central Dispatch, and ask it to run our UI code in the UI thead, like this:
// set UI elements
// on the main thread
dispatch_async(dispatch_get_main_queue()) { () -> Void in
    self.labelName.text = nameRead!
    self.labelAge.text = String(ageRead!)
    self.labelGender.text = genderRead!
    self.labelCountry.text = countryRead!
}

So now, the entire code our of our entire application looks like this:
import UIKit
class ViewController: UIViewController
{

    @IBOutlet weak var labelName: UILabel!
    @IBOutlet weak var labelAge: UILabel!
    @IBOutlet weak var labelGender: UILabel!
    @IBOutlet weak var labelCountry: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)

        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                    
                    // set UI elements
                    // on the main thread
                    dispatch_async(dispatch_get_main_queue()) { () -> Void in
                        self.labelName.text = nameRead!
                        self.labelAge.text = String(ageRead!)
                        self.labelGender.text = genderRead!
                        self.labelCountry.text = countryRead!
                    }
                }
                catch
                {
                    
                }
            }
            else
            {
                print("Error downloading data. Error = \(error)")
            }
        }
        
        // actually execute the task
        downloadTask.resume()
    }
}

And there you have it folks!
Now you can run your app, and the UI will be updating as soon as the data gets downloaded.

On my next tutorial I will be showing you how to return the downloaded data to another class, using your own completion handler.
This is more the likely the pattern you'll be using to download data in a larger app.

Eduardo.

Tuesday, March 24, 2015

Simple NSURLConnection vs NSURLSession example - using Swift

This is a simple example of how to achieve the same simple GET call with (the "old") NSURLConnection and the new NSURLSession.

This project is now on Swift!
(so 50% less code, and 200% more "Why??"s)

This blog post is almost identical to the previous one I made on objective-c (link) but obviously written on swift. The logic and approach are the same.

The final result
This a simple app with 2 buttons, 1 label and 1 UIImageView.




When you tap the "NSURLConnection" the app downloads a picture of a car using NSURLConnection.
When you tap the "NSURLSession" the app downloads a picture of a car using NSURLSession.
The label also updates to tell you which one you're using.

NSURLConnection

Here's the code to get this GET call using NSURLConnection:
@IBAction func button_useNSURLConnection(sender: AnyObject)
{
    let url = NSURL(string: URL_CAR_1)
    var request = NSMutableURLRequest(URL: url!)
    request.HTTPMethod = "GET"
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))
        {
            var responseError: NSError?
            var data = NSURLConnection.sendSynchronousRequest(request,
                returningResponse: AutoreleasingUnsafeMutablePointer<NSURLResponse?>(),
                error: &responseError)
            
            dispatch_async(dispatch_get_main_queue(),
                {
                    self.label_status.text = "Selected NSURLConnection"
                    self.imageView.image = UIImage(data: data!)
                })
        }
}//end button_useNSURLConnection

I decided to make the NSURLConnection a Synchronous request so it can return a value (Asynchronous would just give me a value in a easier way, but that's not fun), but this means it runs in the main thread.
  1. We get a URL from a String (a constant I have defined in my class)
  2. We make a MutableURLRequest with the url of step 1
  3. Set the HTTP method of the request to GET, although this isn't really needed but its good for the tutorial to show how to set the HTTPMethod
  4. Created and used a new GCD queue to make the actual NSURLConnection in a different/background thread
  5. Once the background thread is completed, I'm calling the main GCD queue (main/UI thread) to place the downloaded image into the imageView

NSURLSession

NSURLSession is a bit different. An NSURLSession has be created with a NSURLSessionConfiguration first. Because of this I created a helper method that generates a NSURLSession with the configuration needed, and it returns the NSURLSession it creates:
func getURLSession() -> NSURLSession
{
    var session:NSURLSession
    
    var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    session = NSURLSession(configuration: configuration)
   
    return session;
}//end getURLSession

Since I don't want slow down the main thread for this, I placed this code in a different thread.
Then, here's how to make the NSURLSession call:
@IBAction func button_useNSURLSession(sender: AnyObject)
{
    let url = NSURL(string: URL_CAR_2)
    var request = NSMutableURLRequest(URL: url!)
    request.HTTPMethod = "GET"
    
    var task = getURLSession().dataTaskWithRequest(request)
        { (data, response, error) -> Void in
            
            dispatch_async(dispatch_get_main_queue(),
                {
                    self.label_status.text = "Selected NSURLSession"
                    self.imageView.image = UIImage(data: data!)
                })
        }
    
    task.resume()
}//end button_useNSURLSession

Very similar to the NSURLConnection, so I'll start at the NSURLSessionDataTask.
Technically, NSURLSession does not replace NSURLConnection, but NSURLSessionDataTask is what replaces NSURLConnection.

  1. NSURLSessionDataTask is an Asynchronous task, so we're kind of forced to get the data from its completion handler
  2. Call the main/UI thread and set the value of the imageView with the data from the completion handler
  3. Set the task to [task resume] so it can start
That's all!

The sample code can be found here.


Eduardo.