Friday , 19 July 2019
iOS tutorial, part 2: Creating a web service

iOS tutorial, part 2: Creating a web service

Before jumping into creating a web service, let’s modify the Flickr-based API calls from our previous project to look
into another web service. We’ll explore a more complex response, which will set us up for the meat of this tutorial: creating
our own web service and interacting with it.

The basics remain the same throughout most web
services. For this example, I chose a weather service from World Weather Online and used the free API (there is also a paid service). After you log in to your account, go to the API Explorer. Select the Free Weather API from the dropdown menu and click Get Local Weather (Figure A).

Figure A

FreeWeatherAPI_FigA_090913.png

When you click the Get button to the left of Local Weather,
it expands to show the options your Get HTTP request can
have. Leave the values as-is and
click the Try it! button at the bottom. You should get what you see in Figure B.

Figure B

FreeWeatherAPI_FigB_090913.pngFreeWeatherAPI_FigB_090913.png

This is basically telling us what the API request should be — that is, the Request Uniform Resource Identifier (URI). Copy that link and paste it
into a browser window. You should get a bunch of text that starts like this:

{ "data": { "current_condition"

This is returning a JSON dictionary, which you
must now parse. Let’s take
this URI and put it into our previous Flickr app where the Flickr URL used to
be. Your code will look like this:

- (void)viewDidLoad{
    [super viewDidLoad];
    NSURL *myURL = [NSURL URLWithString:@"http://api.worldweatheronline.com/free/v1/weather.ashx?q=London&format=json&num_of_days=5&key=xfanay7rjhsfe6ays8w3xfza"];

    NSURLRequest *myRequest = [NSURLRequest requestWithURL:myURL];
    NSURLConnection *myConnection = [NSURLConnection connectionWithRequest:myRequest delegate:self];
}

Run your app. It will crash, because we’re still trying to parse our new data
with our old schema or structure. If you had an NSLog for your flickrDictionary in your
connection:didReceiveResponse method, add a breakpoint to that line or just
after it — this will stop the
program execution at that line and let you see the NSLog before the app
crashes. (Breakpoints must be
activated for the execution to stop at each breakpoint.) You can also let it crash and
look in the console; the received
response will still be logged there.

The idea is to see what the new response dictionary looks
like in order to restructure our tableview methods to handle the new
schema. If you look at the
line that logs the string, it looks like unreadable code; however, if you log the dictionary, you can see the structure in a clearer way. The new schema looks something like
this:

1) data

a. current condition

 
i. a
bunch of parameters such as cloudcover

b. request

c. weather

 
i. date
for 5 days since that’s the value we chose

Now comes some thought processing that will cause you to rack your brain. We have a data dictionary with three entries. We want data from
the first entry called “current_condition” — specifically, we want its “weatherDesc” key, so “data.current_condition.weatherDesc” is the branch we wish to reach. My way of doing it will introduce you to a method
called isKindOfClass.

We know our
flickrDictionary is an NSDictionary because I told you to use that
object, but let’s really test
it. Add these lines below your
NSLog flickrDictionary line:

//test
    if ([flickrDictionary isKindOfClass:[NSDictionary class]]) {
        NSLog(@"Yup, its a dictionary alright!");
    }

Run your app and check the console for that text. A quick and easy way to do it,
especially when you have lots of things logging in the console, is to use your console’s Find feature. After the app crashes or stops at the breakpoint, click inside the console to make sure the Find search bar opens for it and not for the editor window on top; then, click Cmd+F to open the search bar on the top right. Copy and paste the text.

So you know it’s a dictionary — big whoop, right? If flickrDictionary is a dictionary, it
must contain entries. One entry you see at the beginning is “data” so let’s get that key’s object and log it. Replace
your //test code block with this:

//test
    if ([flickrDictionary isKindOfClass:[NSDictionary class]]) {
        id object = [flickrDictionary valueForKey:@"data"];
        if ([object isKindOfClass:[NSDictionary class]]) {
            NSLog(@"Again!");
        }
    }

We took the dictionary’s data entry (which we
could see in the console) and put it into an id type object; this means it’s an object of unknown
type. Then, we test if that object
is a dictionary. Run the app and
find the logged text. Let’s
look for the next entry, “current_condition”:

//test
    if ([flickrDictionary isKindOfClass:[NSDictionary class]]) {
        id object = [flickrDictionary valueForKey:@"data"];
        if ([object isKindOfClass:[NSDictionary class]]) {
            id anotherObject = [object valueForKey:@"current_condition"];
            if ([anotherObject isKindOfClass:[NSDictionary class]]) {
                NSLog(@"OMG!");
            }
        }
    }

Run it again. You couldn’t find the “OMG!” text, could
you? That’s because that next
object is not a dictionary. Replace the NSDictionary class for NSArray class in that last if test
and check the console again. This is a handy way of testing for unknown
objects. Web services are
notorious for returning JSON strings, which are not very human readable. Remember: It’s always good to
test.

Now we have an array
inside the “current_condition” entry. One way to tell them apart is that dictionaries start with a “{” curly
brace whereas arrays start with a “(” or “[“. So this object inside “current_condition” is an array, which
has objects at certain indices instead of values or objects for keys. To get our first object from that
array, we will do this:

if ([flickrDictionary isKindOfClass:[NSDictionary class]]) {
        id object = [flickrDictionary valueForKey:@"data"];
        if ([object isKindOfClass:[NSDictionary class]]) {
            id anotherObject = [object valueForKey:@"current_condition"];
            if ([anotherObject isKindOfClass:[NSArray class]]) {
                id firstArrayObject = [anotherObject objectAtIndex:0];
                if ([firstArrayObject isKindOfClass:[NSDictionary class]]) {
                    NSLog(@"I cant take this anymore");
                }
            }
        }
    }

Run it and find your text. Now we’re starting to get a feel for what a meaty
server response looks like.  

We would like to get the value for weatherDesc,
which is an entry inside this new dictionary. But weatherDesc’s value is another array with a dictionary at
its index:0, which itself has a dictionary consisting of only one entry called “value”. That’s the one we
want, and this is how we get it:

//test
    if ([flickrDictionary isKindOfClass:[NSDictionary class]]) {
        id object = [flickrDictionary valueForKey:@"data"];
        if ([object isKindOfClass:[NSDictionary class]]) {
            id anotherObject = [object valueForKey:@"current_condition"];
            if ([anotherObject isKindOfClass:[NSArray class]]) {
                id firstArrayObject = [anotherObject objectAtIndex:0];
                if ([firstArrayObject isKindOfClass:[NSDictionary class]]) {
                    NSLog(@"value is %@", [[[firstArrayObject objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"]);
                }
            }
        }
    }

If we were to get the value in one fell swoop, it would
look something like this:

NSLog(@"value is %@", [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"current_condition"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"]);

Now we have the value for today’s (current condition) weather: “Clear”. (It would have been a lot
easier to just open the window, wouldn’t it?) Let’s create a mutable array to put the “Clear” value into
it. If you’re using the old project as a starting point, you’ll have an NSMutableArray called cFRAIPArray
already in there as an ivar. Let’s take our “Clear” value and put it inside:

NSString *today = [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"current_condition"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"];
    [cFRAIPArray addObject:today];

Let’s get the forecasts for the next five days. If you look at the logged flickrDictionary in the console, you’ll see that at the same hierarchical level as current_condition, you have
a dictionary called “weather”; its
contents reside in an array with many objects, each corresponding to a
different day. So translated to
code, the next value we would need is called:

NSString *tomorrow = [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"weather"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"];
    NSLog(@"tomorrow will be %@", tomorrow);

Run and find tomorrow in the console. Now we can add this to the our
cFRAIPArray and get the next three days. Remember our URI called for 5 days and day 1 is current_condition, so our complete method would look like this:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"data is %@", data);
    NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"string is %@", myString);
    NSError *e = nil;

    flickrDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&e];
    NSLog(@"dictionary is %@", flickrDictionary);
    
    //Init array
    cFRAIPArray = [[NSMutableArray alloc] initWithCapacity:6];
    
    //DAY1
    NSString *today = [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"current_condition"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"];
    [cFRAIPArray addObject:today];
    //DAY2
    NSString *tomorrow = [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"weather"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"];
    [cFRAIPArray addObject:tomorrow];
    //DAY3
    NSString *afterTomorrow = [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"weather"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"];
    [cFRAIPArray addObject:afterTomorrow];
    //DAY4
    NSString *next = [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"weather"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"];
    [cFRAIPArray addObject:next];
    //DAY5
    NSString *afterThat = [[[[[[flickrDictionary valueForKey:@"data"] objectForKey:@"weather"] objectAtIndex:0] objectForKey:@"weatherDesc"] objectAtIndex:0] objectForKey:@"value"];
    [cFRAIPArray addObject:afterThat];
    
    NSLog(@"cFRAIPArray is %@", cFRAIPArray);
    
}

This produces a nice array with the weather conditions for the next five days at this location. Let’s remove some old code we had in
connectionDidFinishLoading and leave it clean, like this:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // do something with the data
    // receivedData is declared as a method instance elsewhere
    NSLog(@"Succeeded!");

    [self.tableView reloadData];
}

We can do this because we have all of our array loading code
in the connection:didReceiveData. As this loads and finally reaches the connectionDidFinishLoading, the
tableview’s reloadData method is called to refresh the view with the finalized
data.

Leave a Reply

Your email address will not be published. Required fields are marked *

*