views:

3032

answers:

11

Hi everyone,

I am having a heck of time with something that seems rather
straightforward but I can't seem to get working. I am building an
iPhone app that retrieves data from a web host. I am trying to
establish an asynchronous connection to the host as I want to keep the
device freed up during the connection. (sendSynchronousRequest freezes
the phone until the request is done.) Here is my connection code:

//temp url to see if data is returned:
NSURL *theURL = [NSURL URLWithString:@"http://www.theappleblog.com/feed"];

NSURLRequest *dataRequest = [NSURLRequest requestWithURL:theURL
             cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
             timeoutInterval:60];

/* establish the connection */  
theConnection = [[NSURLConnection alloc]
                 initWithRequest:dataRequest
                        delegate:self
                startImmediately:YES];

if (theConnection == nil) { 
    NSLog(@"Connection Failure!");
    self.urlData = nil; 
} else {
    self.urlData = [[NSMutableData data] retain];   
}

I have all of the appropriate delegate methods set up:

-(void)connection:(NSURLConnection *)connection
       didReceiveResponse:(NSURLResponse*)response
{
    [urlData setLength:0];
    UIApplication *application = [UIApplication sharedApplication];
    application.networkActivityIndicatorVisible = YES;
    NSLog(@"Received Response!");
}

-(void)connection:(NSURLConnection *)connection
       didReceiveData:(NSData*)incrementalData
{
    [self.urlData appendData:incrementalData];

    NSNumber *resourceLength = [NSNumber
              numberWithUnsignedInteger:[self.urlData length]];
    NSLog(@"resourceData length: %d", [resourceLength intValue]);
    NSLog(@"filesize: %d", self.urlDataSize);
    NSLog(@"float filesize: %f", [self.urlDataSize floatValue]);
}

-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
    NSLog(@"Connection finished loading\n");
    NSLog(@"Succeeded! Received %d bytes of data",[urlData length]);
    _isFinished = YES;
    UIApplication *application = [UIApplication sharedApplication];
    application.networkActivityIndicatorVisible = NO;
}

- (void)connection:(NSURLConnection *)connection
        didFailWithError:(NSError *)error
{
    NSLog(@"Error: %@",[error localizedDescription]);
}

As you can see, I've got a boat-load of log messages because I wanted
to see if anything was coming through at all. My connection test
comes back as TRUE but no data ever gets loaded. Like I said, I'm sure
I must be doing (or not doing) something really stupid. But what?
Any help would be most appreciated.

Thanks, Lawrence

A: 

Initial thought - is the object that has all this code being retained properly? It could be it's being released, and thus deallocating the connection...

Otherwise you should at least see the response.

Kendall Helmstetter Gelner
I initialize the rssData with "nonatomic and retain" in my .h interface file.I am not getting ANYTHING back on any of the delegate methods.
Leachy Peachy
Add an NSLog in the class dealloc method (the same class with those methods) to make totally sure it's not being released... that's the only explanation I can think of.When all other explanations fail, then whatever remains (no matter how improbable) is the truth.
Kendall Helmstetter Gelner
That was a great suggestion. Unfortunately, nothing is still being returned by any of my delegate methods. I don't get it. But I will.
Leachy Peachy
A: 

Nothing obviously wrong up there. I have code that's substantially the same and works fine. The only differences I see are:

  • I use NSURLRequestUseProtocolCachePolicy instead of NSURLRequestReloadIgnoringLocalCacheData

  • I use connection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self]; instead of specifying startImmediately: YES (since it's the default)

It would be helpful to know which, if any, of your delegate methods are getting called. At the very least, you ought to get at least one or the other of connectionDidFinishLoading or connectionDidFail being called. If not, then something's probably wrong with your delegate setup - are you ABSOLUTELY sure that the delegate method signatures are correct, and that you're passing in the right item as the delegate?

Mark Bessey
Hi Mark,None of my delegate methods appear to be getting called. I'm sorry for the horrible formatting of my question. I just joined the site and I just screwed the pooch on that one.I had tried the same caching policy you use but no luck. Here are my method signatures:-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data-(void)connectionDidFinishLoading:(NSURLConnection *)connection-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
Leachy Peachy
Hmm. Maybe try steping through the setup in the debugger, and see if any of your intermediate values come up as nil? Honestly, it looks like ought to work...
Mark Bessey
A: 

It's hard to zero in on anything since it's not clear from your question which, if any of the NSLog statements executes. Please be a little more clear about the results you're getting.

From a quick look the only thing that looks like a potential problem is that you have a race condition when you set up the connection. If the network is especially fast there's a slim but nonzero chance that -connection: didReceiveData: will be called before self.urlData has been allocated. I'd suggest allocating urlData either before initiating the connection or in -connection:didReceiveResponse: to prevent this.

If that doesn't fix it, more information from you is needed.

Tom Harrington
I don't think there's actually a race condition there. The connection:didReceiveData: method is called on the main thread. The socket listener is running in its own thread, but it posts notifications to the main thread's runloop.
Mark Bessey
NONE of my NSLog statements actually execute. I am initializing the rssData variable in my interface .h file. Should I not do that?
Leachy Peachy
+1  A: 

This doesn't answer your question directly, but you might consider looking into Ben Copsey's ASIHTTPRequest library, which wraps around CFNetwork classes and includes asynchronous, threaded request code that takes a lot of work out of doing this properly.

Alex Reynolds
A: 

You say this code works fine using the asynchronous method? Testing that URL, it redirects to another, so what if you implement the redirection delegate methods? What if you try some different URLs?

Mike Abdullah
A: 

I am actually facing the same problem but from a different point of view. The same code is doing well on the iPhone Simulator but is not calling any delegate method (as you explained) when running on a iPhone device! If someone has an idea how to fix this issue. Is NSURLConnection the good way to retrieve data from a Web server or should I use CFNetwork which seems much more network oriented (and I am not a network person...). Any help would be appreciated!

A: 

Is the app otherwise responsive? Is the runloop running? What does the app do after starting the request -- does it return from its event handler back to the runloop? (If not, you'll never get any of your callbacks, because they're delivered via the runloop.)

Jens Alfke
A: 

NSURLConnection is designed to work with any type of protocol, not just HTTP. This means that connection:didFail:withError is called for connection errors, but not for protocol errors. You're not checking for protocol errors -- you have to catch them in connection:didReceiveResponse:. The trick is that the response object that is passed in is actually a NSHTTPURLResponse object. If you check the response's statusCode, I'll bet you're getting an HTTP error such as 404 or server 500.

Neil Mix
+2  A: 

A few suggestions:

  • In your first snippet, instead of theConnection try assigning to self.theConnection to make sure the property accessor methods are called. In various places you also interchange self.urlData and urlData. Might want to make them all consistent by going through self.

  • You may want to make the dataRequest a property as well and go through self to make sure it gets retained.

  • self.urlData = [[NSMutableData data] retain]; -- you shouldn't need that extra retain. Going through self does it for you. The extra retain will make it so the object will stick around and leak even when all is done.

  • On a related note, in your NSLogs make sure you print out the retain count for each object. Also, you may want to have a general clean-up routine around to release these structures (or sets self.foo = nil) so at any point if it barfs out you can call the routine to get things cleaned up.

  • Get a copy of Charles or WireShark (or run tcpdump in the console) and watch the network traffic. It'll help pinpoint at what stage in the flow it's failing.

Ramin
+2  A: 

The problem was that I was continuing program flow and not stopping to allow the connection to finish downloading. Thank you for all of the suggestions.

Leachy Peachy
A: 

I'm having the same problem. I'm starting the [NSURLRequest requestWithURL] call in a singleton object in the main thread. It's not being released. I'm just getting no callbacks to the delegate (self) at all. What exactly do you mean by "continuing program flow and not stopping to allow the connection to finish downloading" ?

Thanks,

Russell.

Hi Russell,My problem turned out to be an error in program flow. I wasn't allowing time for the NSURLConnection to finish before I was moving on in my program. You ought to take a look at <a href="http://allseeing-i.com/ASIHTTPRequest/">ASIHTTPRequest</a>. It will take care of releasing the connection for you. (It's just a wrapper for NSURLConnection.) Let me know if this helps your or not.
Leachy Peachy
Hey, thanks for the answer. ASIHTTPRequest solves everything. Thanks.