Why NSURLConnection blocks my UI? - iphone

I've been reading several threads and questions about this issue but I didn't find the solution.
I have some asynchronous calls performed with
[NSURLConnection connectionWithRequest:anURLRequest delegate:self];
The problem is that I want the interface to be operative but it is blocked until the connection is finished.
Is this solved launching another thread? Where is exactly the problem?
EDIT
Ok, after retrieve data I parse it with NSXMLParser, that do it synchronously and blocks main thread. Is this correct? Then, maybe I need to parse in another thread. Anyone has a guide?

From the docs:
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.
Are you sure that this code is being called on a run loop in default mode and not from a thread created by yourself with a different type of run loop mode?

The UI should not be locking up when you use connectionWithRequest. Try creating a label in your UI and have your connection update it with the current amount of data, like so:
- (void)downloadContentFromUrl:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) {
receivedData = [[NSMutableData data] retain];
self.downloadProgressLabel.text = #"Downloading...";
} else {
// oh noes!
}
}
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[receivedData setLength:0];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
int kb = [receivedData length] / 1024;
self.downloadProgressLabel.text = [NSString stringWithFormat:#"Downloaded\n%d kB", kb];
}
connectionWithRequest does indeed run in it's own thread - no need for you to worry about this. In fact it must be started from the main thread. Check out the NSUrlConnection doc for more info.

+ (id)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate];
This method should create an asynchronous-request (that means that it runs in the background and it doesn't block the UI). You should check if there's another class/method in your file that blocks the UI (for example NSData's '+ (NSData *)dataWithContentsOfURL:(NSURL *)URL').

I don't know if it could help anyone, but I've the same problem (asynchronous URL request blocking the UI) but it was due to:
NSLog(#"dataReceived: %#", data);
in the connectionDidReceiveData method.

In my case I was trying to update the UIProgressView.progress property. I calculated the new value like that
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.progress setProgress:self.downloadedData.length / self.fileSize ];
[self.downloadedData appendData:data];
}
Which doesn't work, I replaced this snippet of code with
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
self.downloadedSize += data.length;
[self.progress setProgress:self.downloadedSize / self.fileSize ];
[self.downloadedData appendData:data];
}
And now the progress view updates with no problem.

Related

NSURLConnectionDownloadDelegate method not getting called

i m working on a project that connect to server and download data from it. i like to support resume download if connection gets interrupt. my approach is to save the downloaded portion of data to a destination file; and if connection gets interrupted, i want to mark the downloaded portion using connection:didWriteData:totalBytesWritten:expectedTotalBytes and later resume from the stopped portion with server.
my code:
- (IBAction)connectToServer:(UIButton *)sender
{
// setup url and send request to server
NSURL *url = [NSURL URLWithString:BASED_URL];
self.urlRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
self.urlConnection = [[NSURLConnection alloc] initWithRequest:self.urlRequest delegate:self];
// start receive data if connection established
if (self.urlConnection){
self.receivedData = [NSMutableData data];
NSLog(#"starting to receive data");
} else {
// handle error
NSLog(#"failed to connect to server");
}
}
- (void)doSomethingWithData
{
// handle data here
}
#pragma NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// received data
[self.receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// error connection
NSLog(#"connection failed");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Data receiving succeed, received: %d bytes of data", [self.receivedData length]);
}
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes
{
NSLog(#"not getting called");
}
my question is how come the "connection:didWriteData:totalBytesWritten:expectedTotalBytes" method never get called?
thanks so much!
chris
Have u added this in .h file:
#interface yourViewContoller : UIViewController <NSURLConnectionDataDelegate, NSURLConnectionDelegate>
According to Apple's documentation:
The NSURLConnectionDownloadDelegate protocol describes methods that should be implemented by the delegate of instances of NSURLConnection created using Newsstand Kit’s downloadWithDelegate: method.
Also, it says:
If you are using NSURLConnection directly, your delegate class should instead implement the methods defined in the NSURLConnectionDataDelegate protocol.

Call webservice repeatingly, objective c

I have a webservice that returning 20 results each time (it is a limitation of the service provider). I want to call this service 10-20 times repeatingly and update my UI each time.
Is there best practice for this situation? I do not want to block the ui while calling the server. This causes problems if the user want to perform actions while the action in progress
(like navigating away from the current page)
Thanks!!!
what you can do is call the webservice in a background thread, collect the required data and jump back to main thread and update the UI.
We are doing the above(i.e jumping from background thread to main thread) because it is not recommended to update any UI in the background process.
you can call you webService in background by using
[self performSelectorInBackground:#selector(MyWebService) withObject:nil];//you can pass any object if you have
and to come back on main thread when the background task is over you can do..
[self performSelectorOnMainThread:#selector(myMainFunction) withObject:nil waitUntilDone:YES];
you can change the last parameter i.e. waitUntilDone:No also. By doing this, user will not have to wait till the UI is updated. they can carry there task.
you can use NSTimer for periodic calling your webService.
hope that helped :)
It depends on how you want to display the information.
If you're using the asynchronous connection (in my opinion, more effective than calling a synchronous connection in the background) and its delegate, it should not block the user interface:
- (void)loadData {
NSString *urlString = #"http://www.stackoverflow.com";
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
}
// delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// clear out or intialize instance data variable
[myData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[myData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// convert data to whatever it's supposed to be (for example, array)
NSString *dataString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
NSArray *dataArray = [parser parseStringToArray:dataString];
[myArray addObjectsFromArray:dataArray];
//update tableview either using reload data (instant) or using updates (for smooth animation)
}
You can then recall the loadData method at the end of didFinishLoading: method to loop it.

How to implement "check for updates" concept in iphone app

I have pickerview which is showing times in minuts like (5,10,20). If user will select any of the time he will get updates after selected time. And my application will run as usual but after 5 minutes a message will show some updates occured.
How I will implement this concept? What coding should I do?
As per my guess should I go with threading?
You could have a NSTimer which starts when you choose a time from your pickerview.
timer = [NSTimer scheduledTimerWithTimeInterval:pickerValueInSeconds target:self selector:#selector(updateMethod) userInfo:nil repeats:NO];
You choose no repeat, then in your updateMethod you make a request:
-(void)updateMethod
{
NSString *url = #"url...";
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:someURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0]; // with some timeout interval.
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if(theConnection)
{
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
systemsData = [[NSMutableData data] retain];
}
else
{
// Inform the user that the connection failed.
NSLog(#"NSURLConnection failed!");
}
}
Now in your
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
and
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
you take care of the data or error and then start a new timer..
Note that you also need to implement
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[systemsData setLength:0];
}
and
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[systemsData appendData:data];
}
You can use a NSTimer.
You can read more about NSTimer here on stackoverflow or on the docs.
You can create a NSTimer and wait for the selected duration. Once timeout is completed
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
the message is fired to the selector. There are plenty of sample codes on google.

Multiple NSURLConnection & NSRunLoop

I am trying to speed up my application download speed. I used Asynchronous NSURLConnection to download contents from the server, it was working fine with one connection.
I use the code from this post to implement multiple delegate objects. Multiple NSURLConnection delegates in Objective-C
When I created 2 NSURLConnection objects, each one is trying to download different files.
The callback didReceiveData routine was called but the it only received data of the first NSURLConnection object until the first connection was done then it started to receive the data from the second NSURLConnection. I want these two connections to receive data at the same time,what should I do? Here is my current code.
-(IBAction) startDownloadClicked :(id) sender
{
while (bDownloading)
{
int nCurrentCon = 0;
while (nCurrentCon < 2)
{
[self downloadAFile:[filenameArray objectAtIndex:nCurrentCon]];
nCurrentCon++;
}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
}
}
- (void) downloadAFile: (NSString*) filename
{
NSString* urlstr = #"ftp://myftpusername:password#hostname";
NSURLRequest* myreq = [NSURLRequest requestWithURL:[NSURL URLWithString:urlstr]];
DownloadDelegate* dd = [[DownloadDelegate alloc] init]; //create delegate object
MyURLConnection* myConnection = [[MyURLConnection alloc] initWithRequest:myreq delegate:dd
startImmediately:YES];
}
Then in my Delegate Object, I implemented these routines
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[receiveBuffer setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"receiving data for %#", targetFileName); //the file name were set when this delegate object is initialized.
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Download Failed with Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"File %# - downloaded.", targetFileName);
}
Your code looks okay. I have a similar setup that works successfully (although there seems to be a limit of four concurrent conections).
The main difference between your and my code is that you use FTP while I use HTTP. Why don't you try it with HTTP connections just to see whether you have run into a restriction of FTP connections on the iPhone?

NSURLConnection doesn't call delegate methods

I saw similar questions here, but I couldn't find solution to my problem.
I have a simple NSURLConnection in main thread (At least I didn't create any other threads), but my delegate methods aren't get called
[[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
and no methods called, e.g.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"didReceiveResponse");
}
self is also a delegate for NSXMLParser, but I think it should not be a problem, as I have this working in my other class in the same project. I checked everything 10 times already, but can't find any problem.
I've seen some hack to solve it here: http://www.depl0y.com/?p=345 but I don't like it, May be someone knows better solution? thanks
The only reason I know is a separate thread (that is already terminated when the delegate methods are called).
Try to NSLog(#"Is%# main thread", ([NSThread isMainThread] ? #"" : #" NOT"));right before the url connection creation
Try running your connection on main thread:
NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
[connection start];
The autorelease is dangerous. The calls to the delegate are made after your function returns (asynchronously). Are you retaining it somewhere else?
You have to release the NSURLConnection object in the - (void)connectionDidFinishLoading:(NSURLConnection *)connection callback as pointed out in the Apple documentation, not elsewhere:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Do whatever you want here
// Release the connection
[connection release];
}
Don't release it with autorelease, as Lou Franco suggested.
If it is not the problem, then maybe you have to implement all the required methods in the delegate class:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
The delegate is retained by NSURLConnection so you don't have to worry about it.
I think you may have missed NSURLConnectionDelegate in your class header file.
For example:
#interface yourClass : NSObject <NSURLConnectionDelegate>