PDA

View Full Version : NSURLConnection, delegate methods dont get called




estupefactika
May 14, 2009, 10:29 AM
Hi, I've setup a class as a delegate for NSURLConnection, I
create the NSURLConnection, provide it a delegate (self) but the
delegate methods don't get called (didReceiveData and DidFinishLoading), any idea? Thanks



//In my method
for (int i=0;i<[feedArray count];i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
imgUrl = [NSURL URLWithString:[[mainDelegate.feedArray objectAtIndex:i] objectForKey:@"img"]];

AsyncImageView *async=[[AsyncImageView alloc] init];
[async loadImageFromURL:imgUrl];
[async release];
[pool release];
}




//My class
@implementation AsyncImageView
@synthesize urlImg;
- (id) init
{

if (self = [super init]) {
appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
}
return self;
}


- (void)loadImageFromURL:(NSURL*)url{

if (connection!=nil) { [connection release]; }
if (data!=nil) { [data release]; }
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];

connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

//I can see in logs some like:
//<NSURLConnection: 0x10d19d0, http://www.myurl.com/images/thumbnails/08_01.jpg>


//TODO error handling, what if connection is nil?
}


- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (data==nil) {
data = [[NSMutableData alloc] initWithCapacity:2048];
}
[data appendData:incrementalData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {


[connection release];
connection=nil;
UIImage *img=[UIImage imageWithData:data];
.....

[data release];
data=nil;

}

- (void)dealloc {
[connection cancel];
[connection release];
[data release];
[super dealloc];
}



robbieduncan
May 14, 2009, 10:32 AM
What about the other delegate methods. In particular connection:didFailWithError:?

jnic
May 14, 2009, 10:36 AM
You're using the wrong method signatures for your delegate.

http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html

dejo
May 14, 2009, 10:50 AM
You're using the wrong method signatures for your delegate.
Actually, I believe the signatures are okay (parameter names don't have to match the ref) but the problem is "theConnection" is the parameter name for -(void)connectionDidFinishLoading:(NSURLConnection*)theConnection but then the code of the method uses "connection". So, you'd want:
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {


[theConnection release];
theConnection =nil;
UIImage *img=[UIImage imageWithData:data];
.....

[data release];
data=nil;

}

jnic
May 14, 2009, 10:59 AM
Actually, I believe the signatures are okay (parameter names don't have to match the ref)

Cool, learnt something new :)

dejo
May 14, 2009, 11:02 AM
Cool, learnt something new :)
Not that I recommend using different names. I normally copy the signature right out of the class reference just to make sure I am getting it exactly right.

estupefactika
May 14, 2009, 11:58 AM
Actually, I believe the signatures are okay (parameter names don't have to match the ref) but the problem is "theConnection" is the parameter name for -(void)connectionDidFinishLoading:(NSURLConnection*)theConnection but then the code of the method uses "connection". So, you'd want:
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {


[theConnection release];
theConnection =nil;
UIImage *img=[UIImage imageWithData:data];
.....

[data release];
data=nil;

}


Ok, Ive tried this, now Im testint retainCount of connection. I have a question, why when I alloc a new connection the retainCount is 2??

connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(@"%d",[connection retainCount]); //Result is 2. Shouldn't it be 1?


I think delegate dont works because I use this class twice. When I alloc the connection retainCount is 2 (I dont know why..), the firts time it works the methods delegate, but method dealloc is not called, the objects arent released because retaintcount dont down to 0, so when I use the class by second time, once loadImageFromUrl has finished, method dealloc is called immediately and release the objects... by this delegate dont works, the connection is cancel

My question is Why when I alloc the connection retainCount is 2? Sorry for my english...

dejo
May 14, 2009, 12:05 PM
Ok, Ive tried this, now Im testint retainCount of connection. I have a question, why when I alloc a new connection the retainCount is 2??

connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(@"%d",[connection retainCount]); //Result is 2. Shouldn't it be 1?

Please see this post (http://forums.macrumors.com/showpost.php?p=7206466&postcount=2).

jnic
May 14, 2009, 12:29 PM
Please see this post (http://forums.macrumors.com/showpost.php?p=7206466&postcount=2).

Seconded. Also, see Robbie's post above: http://forums.macrumors.com/showpost.php?p=7614234&postcount=2

estupefactika
May 14, 2009, 12:52 PM
Seconded. Also, see Robbie's post above: http://forums.macrumors.com/showpost.php?p=7614234&postcount=2

Yes, sorry. Nothing happens, this method is not called nor, no errors.

Once loadImageFromUrl has finished, immediatelly dealloc release the objects and methods delegate arent't called.


- (void)connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)error
{
// release the connection, and the data object
[theConnection release];
// receivedData is declared as a method instance elsewhere
[data release];
// inform the user
NSLog(@"Connection failed! Error - %@ %@",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}

kainjow
May 14, 2009, 01:24 PM
This is the problem:
AsyncImageView *async=[[AsyncImageView alloc] init];
[async loadImageFromURL:imgUrl];
[async release];
You're releasing the object before the connection finishes.

Either refactor your code, or throw in some NSRunLoop magic (which I suggest not doing).

Peter Maurer
May 14, 2009, 01:30 PM
Actually, I think the problem is that you never -start the connection. While NSURLConnection's convenience method +sendSynchronousRequest:returningResponse:error: starts loading automatically, most of the other ways to create a connection do not. (Hint: there's also -initWithRequest:delegate:startImmediately:, which should be just what you're looking for. ;))

EDIT: Nevertheless, the memory management error kainjow mentions needs fixing, too -- unless you plan on loading stuff synchronously, which your class's name certainly doesn't suggest.

kainjow
May 14, 2009, 01:45 PM
Actually, I think the problem is that you never -start the connection. While NSURLConnection's convenience method +sendSynchronousRequest:returningResponse:error: starts loading automatically, most of the other ways to create a connection do not. (Hint: there's also -initWithRequest:delegate:startImmediately:, which should be just what you're looking for. ;))

I don't think that's a problem. According to the docs on the initWithRequest:delegate: method:

Returns an initialized URL connection and begins to load the data for the URL request.

Peter Maurer
May 14, 2009, 01:54 PM
I don't think that's a problem. According to the docs on the initWithRequest:delegate: method:

Returns an initialized URL connection and begins to load the data for the URL request.

Oops, you're right. My bad, sorry. That's indeed what the docs say.

(Once the memory management stuff is fixed, estupefactika, and just in case it still doesn't work then: Does your delegate get a -connection:didReceiveResponse: message?)

estupefactika
May 18, 2009, 04:53 AM
This is the problem:
AsyncImageView *async=[[AsyncImageView alloc] init];
[async loadImageFromURL:imgUrl];
[async release];
You're releasing the object before the connection finishes.


Ive removed it and it still dont works.

estupefactika
May 18, 2009, 05:12 AM
Oops, you're right. My bad, sorry. That's indeed what the docs say.

(Once the memory management stuff is fixed, estupefactika, and just in case it still doesn't work then: Does your delegate get a -connection:didReceiveResponse: message?)

Ive added:


- (void)connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)error
{
// release the connection, and the data object
[theConnection release];
// receivedData is declared as a method instance elsewhere
[data release];
// inform the user
NSLog(@"Connection failed! Error - %@ %@",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)respons {
NSLog(@"%@",respons);
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(@"%@",challenge);
}


They never aren't called.

Im using this class twice. The first time it works fine, I have a table with an image in each cell, each one it inits the connection, I receive data and it finish loading the image in the cell.

In the second time Im downloading the rest of images in background, is here where it dont works. I have a itineration "for" where I init the connection for each image. Once this method finish:

- (void)loadImageFromURL:(NSURL*)url {

if (connection!=nil) {NSLog(@"0");[connection release]; }
if (data!=nil) {NSLog(@"1");[data release]; }
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

Inmediatelly the method dealloc is called, it is executed a couple of times:

- (void)dealloc {
[connection cancel];
[connection release];
[data release];
[super dealloc];
}


and no delegates are called, nor didFailWithError, nor didReceiveResponse, nor didReceiveAuthenticationChallenge, nor didReceiveData.

Im going to try to make other class to download the rest of images in background, maybe there is any conflict

estupefactika
May 18, 2009, 12:29 PM
I think I've found the problem, where I was calling to my downloadImages method there was a nsautoreleasepool, I think by this my connection was being released. Now I call my method out and it works fine. Thanks



- (void) doNewThreadStuff {

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

if ([mainDelegate.feedArray count] == 0) {
//Parser xml

ParserXML *parser=[[ParserXML alloc] init];
NSString *path=[[NSString alloc] initWithString:URL_XML];
[parser parseXMLFileAtURL:path];
//I was calling downloadImages here
//[self downloadImages];
[parser release];
}

[pool release];
[self performSelectorOnMainThread:@selector(newThreadDone) withObject:nil waitUntilDone:NO];

}

-(void) newThreadDone {
[self downloadImages];
}


-(void) downloadImages {
for (int i=0;i<[mainDelegate.feedArray count];i++) {
imgUrl = [NSURL URLWithString:[[mainDelegate.feedArray objectAtIndex:i] objectForKey:@"img"]];
AsyncImageView *async=[[AsyncImageView alloc] init];
[async loadImageFromURL:imgUrl];
[async release];
}

}

cstromme
Jul 29, 2009, 10:10 AM
Sorry for bumping this thread, but I'm having a very similar problem and I thought it might be better to use this thread than to create a new one.

I have imported a class from this example (http://cocoawithlove.com/2008/09/cocoa-application-driven-by-http-data.html), but I'm having some problems with it.

Basically I want to use this to fetch some data for an iPhone app, so I decided to first just try and use the class in a command line utility. So I created a new project in XCode, added a new class (KommendeFilmer) and used the code from the NewItemsClient class in the linked project. The problem is that it doesn't appear to fire off any of the delegate methods at all.

Here's my KommendeFilmer.m:
#import <Foundation/Foundation.h>
#import "Fetch.h"

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

// insert code here...
Fetch *fetch = [[Fetch alloc] init];

[pool drain];
return 0;
}


And here's my Fetch.m:
//
// Fetch.m
// KommendeFilmer
//
// Created by Christian A. Strømmen on 29.07.09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "Fetch.h"


@implementation Fetch

@synthesize newItems;

- (void)dealloc
{
[newItems release];
[responseData release];
[baseURL release];
[super dealloc];
}

- (id)init
{
if(self = [super init])
{
responseData = [[NSMutableData data] retain];
baseURL = [[NSURL URLWithString:@"http://store.apple.com"] retain];

NSURLRequest *request = [NSURLRequest requestWithURL:baseURL];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(@"Test");
}
return self;
}

- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
[baseURL autorelease];
baseURL = [[request URL] retain];
NSLog(@"Test 2");
return request;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[responseData setLength:0];
NSLog(@"Test 3");
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[responseData appendData:data];
NSLog(@"Test 4");
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// [[NSAlert alertWithError:error] runModal];
NSLog(@"Test 5");
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"Connection did finish loading");
// Once this method is invoked, "responseData" contains the complete result
NSError *error;
NSXMLDocument *document =
[[NSXMLDocument alloc] initWithData:responseData options:NSXMLDocumentTidyHTML error:&error];

// Deliberately ignore error: with most HTML it will be filled numerous
// "tidy" warnings.

NSXMLElement *rootNode = [document rootElement];

NSString *xpathQueryString =
@"//div[@id='newtothestore']/div[@class='modulecontent']/div[@class='list_content']/ul/li/a";
NSArray *newItemsNodes = [rootNode nodesForXPath:xpathQueryString error:&error];

if (error)
{
// [[NSAlert alertWithError:error] runModal];
return;
}

[self willChangeValueForKey:@"newItems"];
[newItems release];
newItems = [[NSMutableArray array] retain];
for (NSXMLElement *node in newItemsNodes)
{
NSString *relativeString = [[node attributeForName:@"href"] stringValue];
NSURL *url = [NSURL URLWithString:relativeString relativeToURL:baseURL];

NSString *linkText = [[node childAtIndex:0] stringValue];

[newItems addObject:
[NSDictionary dictionaryWithObjectsAndKeys:
[url absoluteString], @"linkURL",
linkText, @"linkText",
nil]];
}
[self didChangeValueForKey:@"newItems"];
}

@end


Now, the only log messages I'm getting are "Test". Why aren't any of the delegate methods being run?


Ps. Ignore most of the code in the delegate methods as I haven't changed anything yet, just trying to get it to actually use the delegate methods before I start tinkering with that.

robbieduncan
Jul 29, 2009, 11:31 AM
Because NSURLConnection is asynchronous. It needs a RunLoop to function and takes some time for stuff to happen. You create one then your program instantly ends.

cstromme
Jul 29, 2009, 06:35 PM
Thank you. :)

temawito
Nov 24, 2009, 02:48 AM
Im having similar problem, when I do connection with the main thread it works, but background connection (another thread) I dont get a call back. What I understood from your answer is that you at the end use the main thread, am I wrong? someone can tell me what happens in this situations? Regards.