iOS Programming Recipe 4: Using NSURLConnection

URL Requests

Making URLS requests on the Mac is as simple as

  1. open terminal.app
  2. then typing the following

$ curl https://alpha-api.app.net/stream/0/posts/stream/global

This retrieves the APP.NET global stream. If you are not familiar with APP.NET, it is a Twitter like service that sports an open api, similar to how Twitter used to be.

The result of running curl is a large JSON string representing the afore mentioned global stream as shown below

curl result

Unfortunately making URL requests in your iOS application isn’t quite this easy, but fortunately NSURLConnection does most of the heavy lifting for you.

NSURLConnection

The NSURLConnection class reference, located on Apple’s developer website has the following description

An NSURLConnection object provides support to perform the loading of a URL request. The interface for NSURLConnection is sparse, providing only the controls to start and cancel asynchronous loads of a URL request.

Which basically says NSURLConnection is used to perform URL requests, but how do you create the actual requests?

NSURLRequest to the rescue!

NSURLRequest

NSURLRequest encapsulates everything needed to define an http request (An in depth explanation of the http protocol is beyond the scope of this tutorial).

For example here are a few of the properties defined on NSURLRequest

  • URL the url of the server to which the request should be sent
  • HTTPMethod The http protocol defines a set of methods that can be performed, the most common are GET, POST, PUT, and DELETE. For our example we will be using GET
  • HTTPBody this defines the data to be sent with the request/response, in the APP.NET example above the JSON response received was the HTTP body.

Using NSURLConnection

Lets create a simple iPhone application that fetches the APP.NET global stream and then displays the result in a text field.

  • Create a new Xcode project using the Single View Application Template, then name it something like urlFetcher
  • Open ViewController.xib and add the following as shown below
    • 2 UIButton’s
    • 1 UITextField
    • 1 UIActivityIndicatorView

viewController.xib

  • Name the buttons Clear & Fetch as shown above
  • If you are using auto layout, you may need to add a horizontal space constraint between the UITextField and the superView as shown below.

Added constraint

  • Select the UIActivityIndicatorView, then open the attributes inspector (option + CMD + 4) and change the style to Gray and then select the Behavior – Hides When Stopped
Create Outlets & Actions
  • Open ViewController.h and add the following properties & method declarations.

@interface ViewController : UIViewController

@property (strong, nonatomic) IBOutlet UITextView               *textField;
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView  *spinner;
@property (strong, nonatomic) IBOutlet UIButton                 *fetchButton;

- (IBAction)makeRequest:(id)sender;
- (IBAction)clear:(id)sender;

@end
  • Open ViewController.xib then (control+drag) from File’s Owner to the UITextField and select textField when the HUD appears.
  • Next (control+drag) from File’s Owner to the UIButton titled “Fetch”, then select fetchButton when the HUD appears
  • Next (control+drag) from File’s Owner to the UIActivityIndicatorView then select spinner when the HUD appears
  • Now we will connect the action methods by first (control+dragging) from the UIButton titled “Clear” to the File’s Owner and then selecting clear: from the HUD menu, then secondly, (control+drag) from the UIButton titled “Fetch” to the File’s Owner and selecting makeRequest:
Setup NSURLConnection Data Delegate
  • Open ViewController.h and add the following protocol to the class declaration

@interface ViewController : UIViewController <NSURLConnectionDataDelegate>

@property (strong, nonatomic) IBOutlet UITextView               *textField;
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView  *spinner;
@property (strong, nonatomic) IBOutlet UIButton                 *fetchButton;

- (IBAction)makeRequest:(id)sender;
- (IBAction)clear:(id)sender;

@end
Creating the URL Request
  • open ViewController.m and add the following 2 properties to the class extension

@interface ViewController ()

@property (nonatomic, strong) NSMutableData   *buffer;
@property (nonatomic, strong) NSURLConnection *connection;

@end

buffer – is where we will store the response data resulting from the request

connection – will be our NSURLConnection

  • Next let’s implement the makeRequest: action method

- (IBAction)makeRequest:(id)sender
{
    /* disable the fetch button during the request */
    [self.fetchButton setEnabled:NO];

    /* begin animating the spinner */
    [self.spinner startAnimating];

    /* create the request */
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://alpha-api.app.net/stream/0/posts/stream/global"]];

    /* create the connection */
    self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

    /* ensure the connection was created */
    if (self.connection)
    {
        /* initialize the buffer */
        self.buffer = [NSMutableData data];

        /* start the request */
        [self.connection start];
    }
    else
    {
        self.textField.text = @"Connection Failed";
    }
}
Code Explanation
  1. disables the fetchButton while the request is being processed (we only need one request at a time)
  2. start animating the spinner
  3. create the url request initializing it with the APP.NET global stream url
  4. create the connection initializing it with the request
  5. check to ensure the request was valid, if so, initialize the data buffer and start the request, if not, write an error to the textField

 

  • Next Implement the clear: action method (trivial)

- (IBAction)clear:(id)sender
{
    self.textField.text = @"";
}
  • Now we need to implement the following 4 methods from the NSURLConnectionDataDelegate, these will allow us to retrieve the url response and monitor the state of the request

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
  • First let’s implement connection:didFailWithError:

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    /* clear the connection &amp; the buffer */
    self.connection = nil;
    self.buffer     = nil;

    self.textField.text = [error localizedDescription];
    NSLog(@"Connection failed! Error - %@ %@",
          [error localizedDescription],
          [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
Code Explanation
  1. This simply terminated the connection and clears the data buffer in the event of an error and logs the error to our text field.

 

  • Next let’s implement connection:didReceiveResponse: which will simply reset the data buffer when a response has been received.

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    /* reset the buffer length each time this is called */
    [self.buffer setLength:0];
}
  • Next we will implement connection:didReceiveData: which is where we will accumulate the received data in to our NSMutableData buffer

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    /* Append data to the buffer */
    [self.buffer appendData:data];
}
  • Finally we will implement connectionDidFinishLoading: which is where most of our logic will be

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    /* dispatch off the main queue for json processing */
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSError *error = nil;
        NSString *jsonString = [[NSJSONSerialization JSONObjectWithData:_buffer options:0 error:&amp;error] description];

        /* dispatch back to the main queue for UI */
        dispatch_async(dispatch_get_main_queue(), ^{

            /* check for a JSON error */
            if (!error)
            {
                self.textField.text = jsonString;
            }
            else
            {
                self.textField.text = [error localizedDescription];
            }

            /* stop animating &amp; re-enable the fetch button */
            [self.spinner stopAnimating];
            [self.fetchButton setEnabled:YES];

            /* clear the connection &amp; the buffer */
            self.connection = nil;
            self.buffer     = nil;
        });

    });
}
Code Explanation

The method above uses GDC (Grand Central Dispatch) is order to move the JSON processing off the main thread as to not block the UI. If you are unfamiliar with GCD, don’t worry this is fairly straight forward.

  1. First we dispatch off to a concurrent queue (off the main thread) to perform the JSON serialization on the data we received in the url response.
  2. Then we dispatch back to the main queue (main thread) to update the UI (UIKit objects are NOT thread safe, they must be used on the main thread).

That’s it!

Build & run then press Fetch and watch the JSON data populate in the text field.

Finished Application

Comments

  1. Best wishes!Your blog is very good!

  2. Wow… I think this is overkill if all you want to do is download some JSON. Just use NSURLConnection’s + sendAsynchronousRequest:queue:completionHandler:

    like this:

        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://alpha-api.app.net/stream/0/posts/stream/global"]];


        [NSURLConnection sendAsynchronousRequest:request
                                           queue:[NSOperationQueue mainQueue]
                               completionHandler:^(NSURLResponse * response, NSData *data, NSError *error)
        {
            if(error == nil){
                NSError *parseError = nil;
                NSJSONSerialization *JSON = [NSJSONSerialization
                                             JSONObjectWithData:data
                                             options:kNilOptions
                                             error:&amp;parseError
                                             ];
               
                NSLog(@"Fetched JSON from %@", request.URL);
                NSLog(@"JSON Data:", JSON);            
            }
            else {
                NSLog(@"Error: %@", error.localizedDescription);
            }
        }];

    And… Is there really a need to use GCD for NSJSONSerialization tasks? The sort of JSON you’ll be downloading from API’s will be short enough to ensure it doesn’t require the computing power that would lock up your UI.

    See the Apple sample code here (look at -getPublicTimeline: in particular):
    (http://developer.apple.com/library/ios/#samplecode/Tweeting/Listings/Tweeting_TweetingViewController_m.html#//apple_ref/doc/uid/DTS40011191-Tweeting_TweetingViewController_m-DontLinkElementID_7) No precautions are taken to do the NSJSONSerialization in the background…

    • Thanks for the comment! You’re right it is definitely overkill for something like downloading app.net’s global stream. My intention was to show how NSURLConnection could be used in general (even though I think the block based approach you show is cleaner). Additionally, as you point out the GCD code is probably not necessary for processing this small amount of json, but I included it to show how to correctly process remote data in cases where the processing can be expensive.

      If we are just talking about a simple GET request to a json web service, I think the following would be the easiest!

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
             
              NSData *jsonData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://alpha-api.app.net/stream/0/posts/stream/global"]];
             
              NSError *error = nil;
              NSString *jsonString = [[NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error] description];
             
              /* dispatch back to the main queue for UI */
              dispatch_async(dispatch_get_main_queue(), ^{
                  if (!error)
                      NSLog(@"%@",jsonString);
                  else
                      NSLog(@"%@",[error localizedDescription]);
              });
          });
  3. Hey, the

     &lt;NSURLConnectionDataDelegate&gt;

    is not woring :/ can i replace it with somehting else? says did you mean NSURLconnectioninternal :/

    • Joe Hoffman says:

      Hi Loki,

      I think your problem is with word press rather than our code. Sometime when we copy paste code into the web editor it replaces < with

      ::CODECOLORER_BLOCK_15::

      and > with

       &gt:

      .

      So try

       <NSURLConnectionDataDelegate>

      instead. Oh, I’ve also fixed the post.

  4. @ShanghaiTimes says:

    NSString *jsonString = [[NSJSONSerialization JSONObjectWithData:_buffer options:0 error:&error] description];

    This last line is throwing an error in XCode 5.0.2/iOS7

    In particular, at 0 error:&error] description];
    It doesn’t like the &error

  5. @ShanghaiTimes says:

    Ok, got over that, but now I get this

    2013-12-30 17:20:17.868 MyWeather[60886:70b] Application windows are expected to have a root view controller at the end of application launch

    Running XCode 5.02

    Any ideas?

  6. Excellent tutorial about NSURLConnection! Thanks!

  7. Anton Kochepasov says:

    Thank you for the good sample and clear explanation. Quite helpful to refresh my iOS knowledge.

  8. I am using NSURLConnection for HTTP calls. Sometimes it’s working perfectly.But randomly getting “Unable to connect to server – Error Error Domain=NSURLErrorDomain Code=-1003 “A server with the specified hostname could not be found.” UserInfo=0x11558f230 {NSUnderlyingError=0x11561a450 “A server with the specified hostname could not be found.” ” error while in browser it’s working fine.
    Also once this error comes it keeps coming and after few minutes it returns response as expected. This is very random behaviour. I tried using AFNetworking also but faced the same issue.

    Can you please help me on it ?

    • Joe Hoffman says:

      HI Shweta,

      I have a sneaking suspicion this is the server and not the App. Are you dynamically creating the URL to make the HTTP call? if so perhaps there’s something in your code that is making an incorrect URL occasionally.

Speak Your Mind

*

css.php
Privacy Policy