Using threads for a JSON request

Make sure you have JSON Framework installed first.

This is a continuation of Post JSON to a webservice

So if you are writting your webservice application and you have to wait a few seconds to make the request and get the response you will notice that your main application gets locked up. This is due to having to wait for the request to complete and nothing else can work till that request is completed.

So here is where we introduce threads. So the idea is this. We will click on a cell and load the view. It will be a blank view and then we will launch a thread to grab the information. Once we have that information the thread will complete and we can load the table.

So we will start off using the following source code. This is code from the UIImageView in a custom cell example.So for here we need to change our PHP JSON endpoint. We want to simulate the wait so we will just toss in a sleep(5); or such. So the endpoint now looks like this

header('Content-type: application/x-json');

$id = (int)$_GET['id'];

$array = array(
        1 => array(
                'id'    => 1,
                'title' => 'Brown Dog',
                'img'   => 'http://iphone.zcentric.com/files/1.jpg',
        ),
        2 => array(
                'id'    => 2,
                'title' => 'Black Dog',
                'img'   => 'http://iphone.zcentric.com/files/2.jpg',
        ),
        3 => array(
                'id'    => 3,
                'title' => 'Two Dogs',
                'img'   => 'http://iphone.zcentric.com/files/3.jpg',
        ),
        4 => array(
                'id'    => 4,
                'title' => 'Girl',
                'img'   => 'http://iphone.zcentric.com/files/4.jpg',
        ),

);

sleep(5);
echo json_encode($array[$id]);

So lets change the endpoint in the code now. Open up JSONViewController.m and in the viewDidLoad method change the NSURL line to look like.

NSURL *jsonURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://iphone.zcentric.com/test-json-get2.php?id=%@", self.itemID, nil]];

You can see the file use to be called test-json-get.php, I just made it test-json-get2.php. The new file has our sleep in it. If you click build and go now, you will notice that if you click on a cell to view it you will experience some delay and the UI is in a locked state till the 5 seconds are up. Once the 5 seconds are up the new view is displayed.

So our goal is to first load that new view and then display like a activity indicator in the upper right. Now you might be saying.. why use threads? Just display the indicator before making the JSON call. Well the reason is while we make the request is that the UI is locked and the indicator will stop so we want to do the request in a background thread so our main UI will continue to work.

So the first thing we want to do is move the JSON code from viewDidLoad into a new method. Lets call it getJSON. So here is what that function should look like

- (void)getJSON {
	NSURL *jsonURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://iphone.zcentric.com/test-json-get2.php?id=%@", self.itemID, nil]];

	NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];

	if (jsonData == nil) {
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Webservice Down" message:@"The webservice you are accessing is down. Please try again later."  delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
		[alert show];
		[alert release];
	}
	else {
		self.jsonItem = [jsonData JSONValue]; 

		// setting up the title
		self.jsonLabel.text = [self.jsonItem objectForKey:@"title"];

		// setting up the image now
		self.jsonImage.image = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: [self.jsonItem objectForKey:@"img"]]]];
	}
}

Your viewDidLoad should now be an empty method. So now lets put the activity indicator in there. So now the function should look like

- (void)viewDidLoad {
	CGRect frame = CGRectMake(0.0, 0.0, 25.0, 25.0);
	UIActivityIndicatorView *loading = [[UIActivityIndicatorView alloc] initWithFrame:frame];
	[loading startAnimating];
	[loading sizeToFit];
	loading.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
								UIViewAutoresizingFlexibleRightMargin |
								UIViewAutoresizingFlexibleTopMargin |
								UIViewAutoresizingFlexibleBottomMargin);

	// initing the bar button
	UIBarButtonItem *loadingView = [[UIBarButtonItem alloc] initWithCustomView:loading];
	[loading release];
	loadingView.target = self;

	self.navigationItem.rightBarButtonItem = loadingView;
}

So you can see we are initing a new UIActivityIndicatorView and then creating a new bar button item and using our new indicator view as the bar item’s view. Then we are placing that item in the right hand side of the navigation bar.

So if we click build and go we will get something like this when we click on a cell.

So now we need to do the threading. So our JSON request got moved into a new function so that is where we will do the bulk of the request. So lets set that up.

At the top of the getJSON method you want to put the following code.

NSAutoreleasePool *pool = [ [ NSAutoreleasePool alloc ] init ];

At the bottom of that method you want to put the following

[pool release];

So we are creating an auto release pool. Any items that we are set to autorelease in-between the two lines above won’t be released until we call [pool release]

So now we just need to init the thread. So at the bottom of viewDidLoad method add the following

[NSThread detachNewThreadSelector: @selector(getJSON) toTarget:self withObject:nil];

So that will call the method getJSON in a new thread. Now when you click build and go and click a cell and wait 5 seconds you will see the following.

You will notice that our indicator is still there, so we should remove that. So at the bottom of getJSON we want to remove it once the request is done. So add the following line

self.navigationItem.rightBarButtonItem = nil;

That is all you need for threads. Very simple huh? As always you can grab the code here.

About mike
Currently works for OpenSky as a Senior Linux Admin. He has a wonderful wife Thanuja and 2 great dogs. His major side project is Photoblog.

Comments

15 Responses to “Using threads for a JSON request”
  1. Rco says:

    Interessing samples.

    In the detail view, the back button in the navbar is not visible but seems to work. I think you haven’t correctly connected the navbaritem property on one of the controller, but I can’t manage which one.
    Do you have any idea?

    Thanks agains for this pieces of code.

    RCO

  2. mple says:

    Hi all

    I would like to parse the following json. But am not able to do it..it is giving error. Can you help me how to parse this json in Objective C

    {“menu”: {
    “header”: “SVG Viewer”,
    “items”: [
    {"id": "Open"},
    {"id": "OpenNew", "label": "Open New"},
    null,
    {"id": "ZoomIn", "label": "Zoom In"},
    {"id": "ZoomOut", "label": "Zoom Out"},
    {"id": "OriginalView", "label": "Original View"},
    null,
    {"id": "Quality"},
    {"id": "Pause"},
    {"id": "Mute"},
    null,
    {"id": "Find", "label": "Find..."},
    {"id": "FindAgain", "label": "Find Again"},
    {"id": "Copy"},
    {"id": "CopyAgain", "label": "Copy Again"},
    {"id": "CopySVG", "label": "Copy SVG"},
    {"id": "ViewSVG", "label": "View SVG"},
    {"id": "ViewSource", "label": "View Source"},
    {"id": "SaveAs", "label": "Save As"},
    null,
    {"id": "Help"},
    {"id": "About", "label": "About Adobe CVG Viewer..."}
    ]
    }}

    thanks in advance….

  3. Andreas Blomqvist says:

    Hi

    If I want to set the accept header type to “application/json” how do I do that? I need to do that since the url I want to call otherwise returns xml.

    Thanks

  4. Ben Sussman says:

    I have been programming for the iPhone for six months or so now, and I have found that this is not the best way to do this. Of course it will definitely work, and you can use performSelectorOnMainThread: to edit the UI from your newly forked thread, and in fact it may be necessary to fork off a thread if your parsing is extremely complex and takes a long time (sound processing for example). However, to simply fetch a URL asynchronously the better solution is to use the NSURLConnection object. You can code it in a very similar way to that described here but the callbacks will occur on the Main thread and will simply be put on the runloop.

  5. Cramberry says:

    Thanks for the tip! I had beens searching Google for the past 10 minutes, trying to find out how to do exactly this.

    Small question: What exactly does the NSAutoreleasePool do?

  6. This is a terrific post. Yours is the first example I’ve seen that emphasizes the behavior of UIActivityIndicatorView if you *don’t* perform your work / activity outside of the gui event thread. I could never figure out why my activity indicator just never ever actually worked after first use. Moved the heavy lifting to another thread and it worked like a charm.

    Thank you so much for this example.

  7. apiel says:

    Helo,

    When I use this lib, I have an error:

    Loading program into debugger…
    GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)
    Copyright 2004 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you are
    welcome to change it and/or distribute copies of it under certain conditions.
    Type “show copying” to see the conditions.
    There is absolutely no warranty for GDB. Type “show warranty” for details.
    This GDB was configured as “i386-apple-darwin”.warning: Unable to read symbols for “/System/Library/Frameworks/UIKit.framework/UIKit” (file not found).
    warning: Unable to read symbols from “UIKit” (not yet mapped into memory).
    warning: Unable to read symbols for “/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics” (file not found).
    warning: Unable to read symbols from “CoreGraphics” (not yet mapped into memory).
    Program loaded.
    sharedlibrary apply-load-rules all
    Attaching to program: `/Users/Anakena/Library/Application Support/iPhone Simulator/User/Applications/A15FB623-E373-4A2C-9278-D36516CF4419/extrapounds.app/extrapounds’, process 4918.
    [Switching to process 4918 thread 0x4703]

    Don’t know realy why? If you can help me :d

    Thx

  8. millenomi says:

    This code is dangerous. Do not use it.

    One: you are accessing UIKit from another thread. UIKIT IS NOT THREAD SAFE. In any way and for all possible meanings of the words “thread safe”.

    Two: no autorelease pool in the new thread. You’re leaking autoreleased objects in getJSON.

    Three, but minor: ‘get…’ has a specific meaning in Objective-C the same way as ‘init…’. It usually means that the method accepts a pointer that it then fills. Look for methods that start with ‘get’ in the docs to see it in action. Try to avoid it in your code.

  9. Rusty says:

    Thanks again homey! You made something so simple and easy, it should have come from Apple themselves (except that the SDK somehow is the opposite of Apple ease of use)

  10. Viossiliaky says:

    I’m the only one in this world. Can please someone join me in this life? Or maybe death…

  11. SqueereSova says:

    This look interesting,so far.
    If there’s anyone else here, let me know.
    Oh, and yes I’m a real person LOL.

    Bye,

  12. Bypeimmerry says:

    Sup i am new on here. I find this board quite useful & it has helped me out loads. i should be able to contribute & help other ppl like it has helped me.

    I love to enjoy family guy full episodes to help spin a lot of time.

    Thanks everyone, See ya around.

  13. Brad says:

    Thanks! Do you know if it’s better to use detachNewThreadSelector as you did than to use performSelectorInBackground?

  14. ChloeQI says:

    I’ve just found this site http://www.get-an-iphone.com, can I really get an iphone for free?.

    Chloe

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!