Software Development optimize

iOS tutorial, part 2: Creating a web service

Learn how to roll your own web service and interact with it in an iOS environment. We use a weather app for the sample data.

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.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.

About

My name is Marcio Valenzuela and I am from Honduras. I have been coding in iOS for 5 years and I previously worked on web applications in ASP and PHP/mySQL. I have a few apps up in the app store and am currently working on a couple of Cocos2d games...

0 comments