Objective C Threading

Associate
Joined
25 Feb 2007
Posts
905
Location
Midlands
Hi,

I'm trying to create an iPhone app that pulls data from a web services as a JSON string, however when I do this, I sometimes get this error:

"Obtaining the web lock from a thread other than the main thread or the web thread. UIKit should not be called from a secondary thread."

I understand what the error is telling me (I think), but don't know how to fix it!

Here's my code I'm using to get the JSON data, which runs on the press of a button after the user fills out some required info:

Code:
if(self.textbox.text){
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_async(queue, ^{
            NSError *error = nil;
            NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"URL-TO-WEB-SERVICE"]];
            NSString *json = [NSString stringWithContentsOfURL:url
                                                      encoding:NSASCIIStringEncoding
                                                         error:&error];

Any advice?

Cheers! :D
 
Well what do you think the error is telling you? :D

I presume there is some coding missing there because your dispatch_async ends abruptly. From your error I'd guess you're trying to set the fields in your interace from within your dispatch_async code block. And as you can guess from the error, you're not allowed to do that. You can only make interface changes on the main thread. There are various ways to solve that but for a start have a look at performSelectorOnMainThread, you can use that to call setText etc on interface elements.
 
Well I think it's telling me that I can't update the UI from anything other than the main thread, but I don't think I'm updating the UI at all...

Yes I did miss out the last bit of code!

Code:
            if(!error) {
                NSData *jsonData = [json dataUsingEncoding:NSASCIIStringEncoding];
                
                [self performSelectorOnMainThread:@selector(fetchedData:) 
                                       withObject:jsonData waitUntilDone:YES];
                
            }
                
        });

I was setting the values of some variables to the the values of text boxes in the interface, which were then used as query parameters when building the url to get the JSON string - is this the issue?

I've since moved that bit out of the dispatch_async code block, so I'm creating the url with parameters before than, and then passing it in as a local variable.

Now I'm getting a different error:

Error Domain=NSCocoaErrorDomain Code=256 "The operation couldn’t be completed.

Thanks!
 
OK, slightly related (rather than opening a new thread).

I've changed dispatch_async to dispatch_sync as it was loading the view before the data had been downloaded.

Dispatch_sync gives the desired effect in that the user can't progress until all the data has been downloaded, however it now looks like my app hangs for a few seconds.

How can I implement a loading bar/graphic to show the user that something is actually happening whilst the data is downloaded?
 
Do you want to actually show the progress of the download? I'd assume so.

I recently had to do the same with Android, and got a bit frustrated that I had write my own class to do it.. but meh, at least it now works exactly as I want!

I've not tried it in obj-c, but this looks to be a similar solution.
 
I don't think it really matters, the user doesn't 'know' that a file is being downloaded, just that it's fetching their data, so a spinny wheel would do :p

I'll take a look at your link, looks useful! Cheers.
 
As Gex link demonstrates, you should really be using NSURLConnection for this. NSURLConnection is built for asynchronous loading so you don't need to worry about background threads as it does it all for you, no need to re-invent the wheel :)

Apple Documentation and example: http://developer.apple.com/library/...LoadingSystem/Tasks/UsingNSURLConnection.html

This will help you achieve the result you are looking for whether it's the activity indicator (spinning wheel) or the progress bar without the 'hang'.

As a wise man once said; Programming beginners are drawn to multithreading like moths to a flame, with similar results - Delphi Architect. I'm not being critical, I'm just warning you that there are a lot of things to think about when getting involved with multithreading so take your time with it and make sure you do your research.
 
So if I use NSURLConnection I don't need to worry about GCD at all?

As you've guessed, I'm very much the beginner, however my choice of multithreading wasn't through choice, more that it seemed to be 'the right way to do things'. If I could I'd avoid it, as I don't think my app is demanding enough to require it.

Don't worry about being critical, I welcome it :D

So NSURLConnection will handle the downloading of the JSON string and I can call my parser and show some sort of loading graphic at the same time?
 
That is correct.

Once you call the init method on NSURLConnection it will start the download of your data and call the delegate methods you implement. There are several delegate methods and they are all described in the documentation I linked to in my previous post.

The general idea of NSURLConnection is that you create it with a URL and a link to your object that's calling it (this object should implement the NSURLCOnnectionDelegate in the header). Once its established a connection it will call - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response, this basically tells you that it's got a connection and it's about to start sending you data.

Next it will start to send you the data in pieces, you need to take each piece that it sends you and join them together. These pieces of data are sent to the - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data delegate method where you should do something along the lines of [receivedData appendData:data]; where receivedData is an instance variable of NSMutableData.

Once it's finished sending you these pieces of data it will tell you that it's either finished loading with the delegate method - (void)connectionDidFinishLoading:(NSURLConnection *)connection or tell you that it's failed with - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error.

Normally what you would do is to take that recievedData and convert it to an NSString and check that the data is valid, but if you want to convert it straight into an NSDictionary you can use NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:recievedData options:kNilOptions error:nil];.

All of the above is called on the main thread so you can do any UI changes like increasing the value of the progress bar, but the downloading of the data is done on a background thread that is maintained by NSURLConnection so you don't get any blocking and stutters etc.

I hope that helps.
 
Last edited:
Hi Tillus,

Only just seen your response to this - thanks!

In the meantime I've implemented NSURLConnection and it works very well, I have it updating the progress bar however I no longer have the 'wait' before next view is loaded.

I think this is because I have the next view connected as a 'segue' on the button press in my storyboard, so it loads the next view and fires NSURLConnection. I think I need to remove the 'segue' and have it load the next view on connectionDidFinishLoading. Is that correct? Any pointers on how I'd implement that?

Cheers!
 
Back
Top Bottom