I am using NSURLConnection to download the files . I have a UILabel in my View which has to display the currently downloading files. The UILabel is getting updated at the starting and the end. Lets say I am download 10 files. I am able to set the Label text before start downloading and after completing the download.
I can understand that, the method which I am trying to call is not running in main thread,
So I have used the following code to make it run in the main thread ,
[_myHome performSelectorOnMainThread:#selector(updateLabel) withObject:nil waitUntilDone:YES];
and method is
- (void) updateLabel
{
_fileName.text =[NSString stringWithFormat:#"%#",fileName];
}
This also seems to be not working. Am I doing anything wrong here ?
Can anyone tell me how to update the label immediately ??
SOLVED : I used NSNotificationCenter to send notifications to other class, which will update the labels immediately. I tried with performSelectorOnMainThread, even if both the updateProgress, performSelectorOnMainThread is in same class.
Any reasons for why it didn't worked out , is most welcome.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//update your label
}
Related
I have a class with a method (getAndPlaySong) that gets an text file from a server, gets a specific string from the text, and uses the string (a URL) to get a music file from the server. The first server call to get the text file is quick so I implemented it synchronously. The second takes longer and I want to update the display while it is occurring.
I want the method to wait for the asynchronous request to finish before it proceeds. I have tried putting a BOOL in the connectionDidFinishLoading method:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[_receivedData length]);
[AppDelegate playAudioWithData: _receivedData];
_dataIsReceived = YES; //BOOL
// release the connection, and the data object
[connection release];
[_receivedData release];
}
And then putting a while loop in the getAndPlaySong method:
[self startRequest: correctSongURL]; starts the request asynchronously
while (!_dataIsReceived) {
//do stuff
}
The result is that the app hangs when it reaches the loop. The loop never terminates and I never get the "Succeeded..." output. I am a little confused and would be grateful for some input.
I don't wish to sound rude, but what you have done is the worst possible thing. Of course the app hangs because it has an infinite loop. Even if the loop terminates eventually, while its in the loop your app is stuck - what if the user tries to do something with your app while its in the middle of the loop?
You need to change your mindset and learn how to use event driven programming with a state machine. That's the paradigm of iOS, you have to knuckle down and do some reading and learning and change the way you think about and construct your programs.
After you make the request you do nothing, i.e. there is no code coming next in your code snippet. Instead your app sits there doing nothing (but still responsive) waiting for the connection to finish (while your app is doing nothing, the OS has created a thread on your behalf and the connection code is executing inside it, when its finished your connection code needs to inform the rest of your code it has finished, you could do this via a delegate for example, or via a notification).
And that is not the only thing it needs to sit and wait for - what if the user taps on the screen? What if the user exits your app? What if ...
Your app needs to be able to responde to all possible events and respond quickly, the connection is just one more event it must listen for and respond to. It can't do that if its in a loop.
Never use loops anywhere like this.
"I want the method to wait for the asynchronous request to finish before it proceeds." No you don't - you can never do anything like this in an application with a GUI. The function must finish executing. You need to implement a state machine, every iOS is a state machine, the application delegate is the main thing driving the state machine, then you add more states and transitions between the state as more events get added to the program.
Look at the app delegate and its methods that tell your code the app entered the foreground, or the background, or there is a low memory situation etc. That's event driven programming, and you need to extend and build on that principle to take into consideration your connection events and any other events.
Sorry if I sound a bit harsh.
Your app is hanging on the Loop because it is infinite.
On iOS, NSURLConnectionDelegate is the way to get a call back when data is received on the connection and to be notified when it is done. And EVERYTHING should be done asynchronously and via the Delegate. That is THE way it is done on iOS.
The results as it streams in should be stored in an NSData object in the didRecieveData method.
Look at the docs on NSURLConnectionDelegate, but here is an outline example:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
int statusCode = [(NSHTTPURLResponse *)response statusCode];
if(statusCode == 404) {
// Post notification and let concerned parties know something went wrong.
[self cleanupConnection];
}
else if( statusCode == 200) {
// Success do something with the data.
requestData = [[NSMutableData alloc] init];
}
else {
[self cleanupConnection];
}
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[requestData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self finishedLoadingAppData];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Connection Attempt Failed with Error:%#", [error localizedDescription]);
}
Two quick options come to mind:
Create a method to call to handle the completed connection
Use the NSNotification API to send a notification when the load is complete
Additionally, you can use the method for NSURLConnection didReceiveData to handle data coming incrementally from the server.
I have a tricky situation in my app . i am making the server call in application did finish launching method based on the server response i need to load different views .my app is working fine in simulator but in device the app getting crashed because of the time taking to get the server response .i tried NSThread to run the server call but screen is becoming wait until it gets the response from the server.how can i move the stuff out the app deleagte method
You need to call
[[NSURLConnection alloc]initWithRequest:request delegate:self];
This is asynchronous request, you can implement delegate methods of NSURLConnection in appdelegate.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
A better approach would be to display some temporary view and make the network request asynchronous or do it on a background thread. iOS will kill your app if you block the main thread for too long (this may be what's happening in your case), and users aren't likely to want to use an app that spends ten or twenty seconds doing what looks like nothing before putting up a useful display. When you get a response from the server, you can then reconfigure your display appropriately.
BTW, it'd be a good idea to state your question explicitly next time. It's a bit hard to know what you're asking for as the question stands now.
If you're targeting iOS 4.0 and greater, you can use Grand Central Dispatch to make your network call on a background thread. Here's a simple example.
// Create a dispatch queue
dispatch_queue_t networkQueue = dispatch_queue_create("NetworkQueue", 0);
dispatch_async(networkQueue, ^ {
// Start your network task here.
// Now when it completes we will dispatch back to the main queue
// and you can perform UI updates
dispatch_async(dispatch_get_main_queue(), ^ {
// Perform UI updates here on the main thread
};
// Release your dispatch queue
dispatch_release(networkQueue);
};
Now while this is going on be sure you've already put some preliminary unpopulated UI on screen. You should also show and hide UIApplication's networkActivityIndicator appropriately, and possibly some "Loading..." UI.
NSURLConnection is your friend. The best way to solve it is to do the server call asynchronous using NSURLConnection.
There is Sample Code on the Apple Developer Connection that downloads the images for each row in a UITableView asynchronously so the UI is more responsive. So if you take a look at that code you should be able to refactor your code.
Loading the server response asynchronously prevents your app from being shut down by the iOS runtime because you are blocking the main thread for more than 20 seconds.
I have a UITabBar Application with two views that load large amounts of data from the web in their "viewWillAppear" methods. I want to show a progress bar or an activity indicator while this data is being retrieved, to make sure the user knows the app isn't frozen.
I am aware that this has been asked before. I simply need some clarification on what seems to be a rather good solution.
I have implimented the code in the example. The question's original asker later solved their problem, by putting the retrieval of data into another "thread". I understand the concept of threads, but I do not know how I would impliment this.
With research, I have found that I need to move all of my heavy data retrieval into a background thread, as all of the UI updating occurs in the main thread.
If one would be so kind as to provide an example for me, I would be very appreciative. I can provide parts of my existing code as necessary.
If you use NSURLConnection it runs on another thread automatically.
in your viewDidLoad:
NSURLRequest *req = [NSURLRequest requestWithURL:theURL];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:req delegate:self];
then you need some custom methods. If you type in -connection and press Esc you'll see all the different methods you can use. There are three you will need with this:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// this is called when there is a response
// if you're collecting data init your NSMutableData here
}
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// each time the connection downloads a
// packet of data it gets send here
// so you can do [myData appendData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
// the connection has finished so you can
// do what you want with the data here
}
That is basically all there is to it. NSURLConnection handles all the multithreading itself and you don't have to worry. Now you can create an activity indicator and display it and it will work because the main thread is empty. :)
I know there are lots of similar questions floating around, but none of the answers seem to fix my problem. I have an app that uses an NSURLConnection to download a file, and then does some calculations on the downloaded file. I set up a UILabel to display the current loading status (eg: "Loading file", "Parsing file"). I update the UILabel in the didReceiveResponse and connectionDidFinishLoading function of the NSURLConnection delegate, as well as some other places in my code. I update it by calling the following function:
[self performSelectorOnMainThread:#selector(updateProgress) withObject:nil waitUntilDone:NO]
where -(void)updateProgress is a function I defined to call [theLabel setNeedsDisplay]. I NSLog'd it, like
NSLog(#"theLabel: %#\n",theLabel.text);
and the information is updated correctly, but the label doesn't actually update in the view. Also, updateProgress is only called AFTER everything is loaded. It updates the label THEN, which is hardly useful. Any suggestions?
The NSURLConnection is blocking the main thread (no updates will be performed on the view until it finishes).
You can perform updateProgress in the background:
[self performSelectorInBackground:#selector(updateProgress) withObject:nil]
The first line of updateProgress should be:
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc]init];
The last lines should be:
[pool release];
pool = nil;
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
Of course, you can also perform the NSURLConnection in the background. Then you can update the label on the main thread.
I'm making an asynchronus POST request in an iPhone app with this call:
[NSURLConnection connectionWithRequest:req delegate:self];
The request seems to get to the server just fine, but none of the delegate methods get hit. I've implemented:
- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
and
- (void) connection:didFailWithError:(NSURLConnection *)connection
None of the log messages for these methods ever appear. The documentation for connectionWithRequest:delegate: says:
delegate
The delegate object for the connection. The delegate will receive delegate messages as the load progresses. Messages to the delegate will be sent on the thread that calls this method. For the connection to work correctly the calling thread’s run loop must be operating in the default run loop mode.
I think the call is being made off of the main thread (is the assumption that, if I didn't start a new thread explicitly, everything runs in one thread correct?), and I checked the thread with this:
CFRunLoopRef loop = CFRunLoopGetMain();
CFStringRef modeString = CFRunLoopCopyCurrentMode(loop);
NSLog(#"The main loop is running in mode: %#", modeString);
Which creates this console output:
2009-09-13 13:32:05.611 Stock Footage[686:20b] The main loop is running in mode: kCFRunLoopDefaultMode
So, that sounds like it is in the default run loop mode. Is there anything else I should look at that could tell me why my delegate methods aren't hit? The delegate is definitely not dying before the request is complete.
Update: CFRunLoopGetCurrent has the mode kCFRunLoopDefaultMode.
Update (5:40 PM Eastern): Well, I've switched to using initWithRequest:delegate: startImmediately in order to get the callbacks going, but I'd still love to see an example of connectionWithRequest:delegate that actually hits the delegate up.
I had a similar problem. The reason my delegates were not being called is that after I called connectionWithRequest, I started a loop that was checking if the data from the connection had been sent. Since I never returned the control to the RunLoop code, the messages left in the queue were never processed, and therefore the delegate's methods were not called either...
Just make sure that you do not do anything heavy after calling connectionWithRequest and everything should work nicely.
Two things jump out at me:
You have the wrong prototype for your error method (should still get called, however):
connection: (NSURLConnection *) connection didFailWithError: (NSError *) error
You should check to make sure you're getting a valid connection back from + connectionWithRequest:delegate:. It's possible it's not even starting the request.