Connection doesn't respond when beeing set from a new thread - iphone

The connection doesn't respond when beeing set from a new thread:
Code 1 (responds fine):
[self setConnection];
}
- (void)setConnection{
NSLog(#"setting myConnection with request");
myConnection = [[[NSURLConnection alloc]initWithRequest:[NSURLRequest requestWithURL:requestURL] delegate:self] autorelease];
}
Log 1:
2010-02-25 10:44:04.384 Untitled[1002:207] setting myConnection with request
2010-02-25 10:44:06.093 Untitled[1002:207] didReceiveResponse
2010-02-25 10:44:06.094 Untitled[1002:207] didReceiveData
2010-02-25 10:44:06.094 Untitled[1002:207] DidFinishLoading
Code 2:
[NSThread detachNewThreadSelector:#selector(setConnection) toTarget:self withObject:nil];
}
- (void)setConnection{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"setting myConnection with request");
myConnection = [[[NSURLConnection alloc]initWithRequest:[NSURLRequest requestWithURL:requestURL] delegate:self] autorelease];
[pool release];
}
Log 2:
2010-02-25 10:40:50.280 Untitled[972:4003] setting myConnection with request
Delegates:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"didReceiveResponse");
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData");
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"didFailWithError");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"DidFinishLoading");
}
Why is that?
What is the correct way for sending a request & receiving a response- without freezing the main thread / UI.

From the NSURLConnection documentation:
For the connection to work correctly
the calling thread’s run loop must be
operating in the default run loop
mode.
and
Note that these delegate methods will
be called on the thread that started
the asynchronous load operation for
the associated NSURLConnection object.
In your case, what probably happen is the thread is finished before your class actually executed all it’s code.
Read this for a solution on how to start the run loop:
http://www.depl0y.com/2009/02/20/nsurlconnection-in-its-own-thread/
Or, create a second thread and avoid issues of runloops altogether by doing a NSURLConnection sendSynchronousRequest:returningResponse:error, for which no special threading or run loop configuration is necessary in the calling thread.
Do not forget to call back to the main thread via performSelectorInMainThread:

It doesn't work because the thread is finished before your class actually executed all it’s code.
All you need to do now is start the run loop so the thread doesn't exit and the download can work.
[[NSRunLoop currentRunLoop] run];
You can see this mini tuto here: NSURLConnection in it's own thread

Related

NSURLConnection doesn't receive data when creating many downloading objects in iOS5

I have been searching for this problem on the SOF for several days and I still have not found the solution (say the same problem) yet.
I'm making and app that downloads 5 images simultaneously in an URL list (each image is on a different server).
I have an ImageDownloader class subclasses NSOperation and implements the NSURLConnectionDataDelegate.
So that I can add an instance of ImageDownloader to an operationQueue in the ViewController and it will run in a separate thread under the operationQueue. The line that add the downloader to the operationQueue is here:
downloader = [[ImageDownloader alloc] init];
[downloader downloadImageWithURL:[controller.URList objectForKey:[NSString stringWithFormat:#"%d",downloadIndex]] queue:queue andTag:downloadIndex + 100]; //my custom initialize
downloader.delegate = self;
[queue addOperation:downloader]; //use the addOperation method
Everything works fine in iOS6 but messed up in iOS5 (5.0 on my test device and 5.1 on my SDK), it just doesn't receive any response nor data by performing the methods didReceiveResponse and didReceiveData at all (these 2 methods are not jumped in).
After the timeout was exceeded, the runloop jumps into didFailWithError method and the program stalls.
As I understand, this means the runloop still runs right?
I tried to print out the error and all I got is: The request timed out.
When I reduce the number of downloading instances to 2 then it runs, but not with >=3 downloading instances.
One more information is that my network connection does limit the number of connection. But it work fine in iOS6, why it just doesn't work on iOS5?
I can still load the web in the simulator while the app is downloading.
So what kind of problem is this and how can I get over this problem?
Thanks in advance.
*Update:* as there are many classes and the problem's not been clearly detected yet, I will share here the whole project. You can download it directly from here:
DownloadingImage
As I just found out, if you're using credentials there is a chance that the server will reject them randomly every once in a while. So if you have a check to make sure previousFailureCount == 0 then you will most likely have a bug.
I've just figured out where my problem is, but not really understand why.
In my ImageDownloader class, I set up a runloop with done and currentRunLoop variables.
In the main method, I have a while loop for forcing the currentRunLoop run.
As I remove those "runLoop" stuffs, the app runs smoothly on both iOS6 and iOS5.
So change the entire ImageDownloader.m with these lines then it works (I commented out some useless (say harmful) lines):
//
// ImageLoader.m
// DownloadImagesTableView
//
// Created by Viet Ta Quoc on 6/25/13.
// Copyright (c) 2013 Viet Ta Quoc. All rights reserved.
//
#import "ImageDownloader.h"
#implementation ImageDownloader
#synthesize downloadData,delegate,queue,done,customTag;
NSRunLoop *currentRunLoop;
-(void)downloadImageWithURL:(NSString *)imageUrl queue:(NSOperationQueue*)opQueue andTag:(int)tag{
self.customTag= tag;
self.queue = opQueue;
// self.done = NO;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageUrl] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection start];
// currentRunLoop = [NSRunLoop currentRunLoop];
NSLog(#"Start downloading image %d...",customTag);
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"Received response...");
downloadData=[[NSMutableData alloc] initWithLength:0];
expectedDataLength=[response expectedContentLength];
NSLog(#"Image %d size: %lld kb",customTag,[response expectedContentLength]/1024);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
float receivedLenght = [data length];
receivedDataLength=(receivedDataLength+receivedLenght);
float progress=(float)receivedDataLength/(float)expectedDataLength;
[delegate updateProgess:progress andIndex:[NSIndexPath indexPathForRow:customTag-100 inSection:0]];
[self.downloadData appendData:data];
// NSLog(#"Percentage of data received of tag %d: %f %%",self.customTag,progress*100);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
[delegate finishedDownloadingImage:downloadData andTag:customTag];
// done = YES;
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Warning" message:#"Network Connection Failed?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:nil, nil];
// NSLog(#"%#",[error debugDescription]);
NSLog(#"Connection failed! Error - %# %#",[error localizedDescription],[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
[alert show];
}
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
NSLog(#"Got here *(*&(**&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&");
}
-(void)main{
// do{
//// NSLog(#"Running....1");
// [currentRunLoop runUntilDate:[NSDate distantFuture]];
// // [currentRunLoop run];
// } while (!done);
// [currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
#end
Thank you guys for your supports.
==================================================================================
P/s: for anyone who interested in this problem, I update here my entire solution: DownloadImage_Final

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.

Why NSURLConnection blocks my UI?

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.

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>