Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSURLConnection didReceiveData not called

I've read through tons of messages saying the same thing all over again : when you use a NSURLConnection, delegate methods are not called. I understand that Apple's doc are incomplete and reference deprecated methods, which is a shame, but I can't seem to find a solution.

Code for the request is there :

// Create request
NSURL *urlObj = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlObj cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];

if (![NSURLConnection canHandleRequest:request]) {
    NSLog(@"Can't handle request...");
    return;
}

// Start connection
dispatch_async(dispatch_get_main_queue(), ^{
    self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; // Edited
});

...and code for the delegate methods is here :

- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response {
    NSLog(@"Receiving response: %@, status %d", [(NSHTTPURLResponse*)response allHeaderFields], [(NSHTTPURLResponse*) response statusCode]);
    self.data = [NSMutableData data];
}

- (void) connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error {
    NSLog(@"Connection failed: %@", error);
    [self _finish];
}

- (void) connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data {
    [data appendData:_data];
}

- (void)connectionDidFinishDownloading:(NSURLConnection *)_connection destinationURL:(NSURL *) destinationURL {
    NSLog(@"Connection done!");
    [self _finish];
}

There's not a lot of error checking here, but I've made sure of a few things :

  • Whatever happens, didReceiveData is never called, so I don't get any data
  • ...but the data is transfered (I checked using tcpdump)
  • ...and the other methods are called successfully.
  • If I use the NSURLConnectionDownloadDelegate instead of NSURLConnectionDataDelegate, everything works but I can't get a hold on the downloaded file (this is a known bug)
  • The request is not deallocated before completion by bad memory management
  • Nothing changes if I use a standard HTML page somewhere on the internet as my URL
  • The request is kicked off from the main queue

I don't want to use a third-party library, as, ultimately, these requests are to be included in a library of my own, and I'd like to minimize the dependencies. If I have to, I'll use CFNetwork directly, but it will be a huge pain in the you-know-what.

If you have any idea, it would help greatly. Thanks!

like image 620
F.X. Avatar asked Jul 10 '12 19:07

F.X.


3 Answers

I ran into the same problem. Very annoying, but it seems that if you implement this method:

- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL

Then connection:didReceiveData: will never be called. You have to use connectionDidFinishLoading: instead... Yes, the docs say it is deprecated, but I think thats only because this method moved from NSURLConnectionDelegate into NSURLConnectionDataDelegate.

like image 87
MikeQ Avatar answered Nov 12 '22 13:11

MikeQ


I like to use the sendAsynchronousRequest method.. there's less information during the connection, but the code is a lot cleaner.

    [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
        if (data){
            //do something with data
        }
        else if (error)
            NSLog(@"%@",error);
    }];

From Apple:

By default, a connection is scheduled on the current thread in the default mode when it is created. If you create a connection with the initWithRequest:delegate:startImmediately: method and provide NO for the startImmediately parameter, you can schedule the connection on a different run loop or mode before starting it with the start method. You can schedule a connection on multiple run loops and modes, or on the same run loop in multiple modes.

Unless there is a reason to explicitly run it in [NSRunLoop currentRunLoop], you can remove these two lines:

[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];

or change the mode to NSDefaultRunLoopMode

like image 35
Jesse Gumpo Avatar answered Nov 12 '22 13:11

Jesse Gumpo


NSURLConnection API says " ..delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object."

Because dispatch_async will start new thread, and NSURLConnection will not pass to that other threat the call backs, so do not use dispatch_async with NSURLConnection.

You do not have to afraid about frozen user interface, NSURLConnection providing only the controls of asynchronous loads.

If you have more files to download, you can start some of connection in first turn, and later they finished, in the connectionDidFinishLoading: method you can start new connections.

int i=0;
for (RetrieveOneDocument *doc in self.documents) {

    if (i<5) {
         [[NSURLConnection alloc] initWithRequest:request delegate:self];
        i++;
    }
}

..

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    ii++;
    if(ii == 5) {
         [[NSURLConnection alloc] initWithRequest:request delegate:self];
        ii=0;
    }
}
like image 1
János Avatar answered Nov 12 '22 14:11

János