Internet connection Error handling

Discussion in 'iOS Programming' started by r00li, Aug 30, 2009.

  1. r00li macrumors member

    Joined:
    Sep 27, 2008
    #1
    I have an application that downloads data from the internet when the user presses a button. I am using NSURLConnection sendSynchronousRequest method to download the data. I need to handle the problems with connection somehow. For now I just check if the data (data is a NSString) is equal to @"". But I don't know if that is OK.

    And then there is another problem that is related to this error handling. In the method that downloads the data I have a [status setText:mad:"Downloading data..."]; (status is a label used to display current status - and it works for errors). This line of code should get executed before sendSynchronousRequest but it doesn't. When I press the button the thing starts downloading but the message Downloading data... doens't appear. Of course the whole application freezes and the user doesn't know why until the data is downloaded or an error message appears. So why doesn't the downloading message appear when I start downloading?
     
  2. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #2
    You shouldn't use the synchronous downloading methods. You should use the asynchronous downloading methods.

    You've just listed two reason why. You can't update the UI while the download is taking place and you can't report useful error messages. Another is that if there is a network problem it can take over a minute for the connection to time out, during which time your app appears hung.
     
  3. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #3
    Implementing asynchronous methods is a lot more work. Basicly I don't even know how to do it. And reading the original documentation isn't realy helping. I need to download a small amount of data - and store it in a NSString (or NSData and then convert it) and that is it for the connection. When the user does something I need to upload a small amount of data (I don't need a response only confirmation that the data was sent) and refresh the view (download the first data again). The whole thing is a little complicated - and I could find any helpful examples.
     
  4. firewood macrumors 604

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #4
    Best way is to use async network calls and callbacks. Async networking is not rocket science: just break your method in two, the first method to start the download/upload and then return. The second method to get the results and then continue where you left off.

    If you can't do that, try doing the all your networking in another thread (NSThread), separate from the main UI thread.

    Don't forget to test both in airplane mode, and with wifi enabled and connected but your router unplugged from the network cable.
     
  5. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #5
    Before I tryed the synchronus download I actualy was using the asynchronus for the test. But I don't remember where I found that code that worked. And I didn't know how to check which connection actualy finished. Because I need multiple connections. And because you can only have one connectionDidFinishLoading I couldn't figure out which connection actualy finished. If I create a connection in method a I can't use that connection to compare it in the connectionDidFinishLoading.
     
  6. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #6
    Of course you can. initWithRequest: returns an id that contains the connection for that request. Save it to a variable accessible throughout the class. And connectionDidFinishLoading: passes you a connection parameter. You can then compare that to the connection you saved previously.
     
  7. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #7
    Well that I could do. but does Objective C support global variables? Because I never saw them. Of course it has instance/class variables that work everywhere in the class but I didn't see any of those simple global variables.
     
  8. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #8
    Since Objective-C is a strict superset of C, which does support global variables, then, yes, it does too. Anything you can do in C you can do in Objective-C. But many would recommend avoiding C's global variables and using a more OOP approach, such as encapsulating such variables into a singleton.

    We would probably need to understand more about your multiple connections and how they are, I'm assuming, spread across multiple classes but I would think that a single class with class variables would be sufficient to handle the request and the response for the NSURLConnection.
     
  9. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #9
    Well I have a little complicated UI. When the user taps the download button I need to download some data. Then I need to parse that data and based on downloaded data create table view Cells. Each cell has three buttons and when the button is pressed I need to send a response with some of the cells information.

    My download method now works with the asynchronus connection but I get some memory leaks now (I didn't have them before). And I can't find how they appear. It is hard to even show up. But I am releasing anything that was created with alloc/init and I can't find any reason for them.

    EDIT: Commented out almost all of my code but I can't find that leak. I it possible that some system library is leaking? Because I can't find any reason for a leak. And I don't know anymore what is wrong. And it appears from nowhere at some point. If I am pressing the donwload button as mad at some point a leak will poup up. But I don't know why. And sometimes it is after few seconds, other times after a minute.
     
  10. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #10
    I am getting mad. I practicaly commented out wverything and the only thing left is the example code from apple. With all retain/release things:D. And the problem is still here. After pressing the donwload button as mad at some point the little memory leak appears. I have enough! I commented out every single line of code that I wrote. The only tinhs are the auto generated things and the code from example at apple.com.
    http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html#//apple_ref/doc/uid/20001836-BAJEAIEE

    But that memory leak is still there!

    EDIT: Commented out the example code and the problem is gone. Can someon please check if the code from the above link gives him memory leaks?
     
  11. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
  12. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #12
    It isn't just one!
    URLConnectionClient
    URLConnectionLoader
    NSCFset
    CFHTTPMessage
    GeneralBlock-80
    ....
    And they are multiplied. I have at least 10 URLConnectionClients...
     
  13. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #13
    I only see a leak on GeneralBlock-3584.

    EDIT: Can you show us your connection:didFailWithError: and connectionDidFinishLoading: ?

    2ND EDIT: Oh, and whatever method does the initWithRequest:delegate: call as well.
     
  14. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #14
    Connection did fail with error:
    Code:
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
    	if (connection == povezava)
    	{
    		[povezava release];	
    		[download release];
    		[status setText:@"Error with the download!"]; //status label
    		[aktivnost stopAnimating]; //Activity indicator
    	}
    }
    Connection did finish loading:
    Code:
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
    	if (connection == povezava)
    	{
    		NSString *prenseni_podatki = [[NSString alloc] initWithData:download encoding:NSASCIIStringEncoding];	
    		
    		SBJSON *parser = [[SBJSON alloc] init]; //JSON parser
    		NSDictionary *data_dict = [parser objectWithString:prenseni_podatki error:nil];
    		NSArray *data_temp = [data_dict objectForKey:@"status"];
    		
    		[povezava release];
    		[prenseni_podatki release];
    		[parser release];
    		[download release];
    		
    		[aktivnost stopAnimating];
    	}
    }
    This method is not complete! But it isn't the reason for the memory leak.

    And the method that calls initiates the download:
    Code:
    -(IBAction)refreshPress
    {
    	[self refreshSettings]; //get the latest user settings
    	status.text = @"Downloading data...";
    	[aktivnost startAnimating];
    	
    	NSString *naslov = [[NSString alloc] initWithFormat:@"http://%@:%@@%@:%@/gui/?list=1",username, password, host, port];
    	NSURLRequest *zahtevek = [NSURLRequest requestWithURL:[NSURL URLWithString:naslov] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
    	[naslov release];
    	povezava = [[NSURLConnection alloc] initWithRequest:zahtevek delegate:self];
    	if (povezava)
    	{
    		download = [[NSMutableData data] retain];
    	}
    	else
    	{
    		status.text = @"Error! Could not connect!";
    		[aktivnost stopAnimating];
    	}
     
  15. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #15
    Shouldn't you be doing some releasing in connection:didFailWithError: and connectionDidFinishLoading: even when connection != povezava ?
     
  16. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
  17. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #17
    Well, what happens then when connection != povezava ?

    EDIT: I've adapted my sample code to be closer to yours (same variable names, etc.) and I'm still not seeing any leaks other than GeneralBlock-3584. But then again my code is much closer to the Apple sample code than yours. I don't have the startAnimating and the parser, for example.
     
  18. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #18
    Download and povezava don't get realeased. But for now this is the only connection! So connection should allways be equal to povezava.

    EDIT: I tryed commenting out the parser and the activity indicator - it didn't help. So they are not responsible. Basicly I tryed commenting out everything but the apple's code. But I never stopped checking if the connection is equal to povezava.
     
  19. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #19
    Here's my non-leaking code (other than that GeneralBlock-3584)

    NSURLConnectionLeakViewController.h:
    Code:
    #import <UIKit/UIKit.h>
    
    @interface NSURLConnectionLeakViewController : UIViewController {
    	NSURLConnection *povezava;
    	NSMutableData *download;
    }
    
    @property (nonatomic, retain) NSURLConnection *povezava;
    @property (nonatomic, retain) NSMutableData *download;
    
    - (IBAction)refreshPress:(id)sender;
    
    @end
    
    NSURLConnectionLeakViewController.m
    Code:
    //
    #import "NSURLConnectionLeakViewController.h"
    
    @implementation NSURLConnectionLeakViewController
    
    @synthesize povezava;
    @synthesize download;
    
    - (void)dealloc {
    	[povezava release];
    	[download release];
        [super dealloc];
    }
    
    - (IBAction)refreshPress:(id)sender {
    	NSLog(@"connectButtonClicked");
    	NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com/"]
    											  cachePolicy:NSURLRequestUseProtocolCachePolicy
    										  timeoutInterval:60.0];
    	povezava=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
    	if (povezava) {
    		download=[[NSMutableData data] retain];
    	} else {
    		// inform the user that the download could not be made
    	}
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
        [download setLength:0];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        [download appendData:data];
    }
    
    - (void)connection:(NSURLConnection *)connection
      didFailWithError:(NSError *)error
    {
        [connection release];
        [download release];
    	
        NSLog(@"Connection failed! Error - %@ %@",
              [error localizedDescription],
              [[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        NSLog(@"Succeeded! Received %d bytes of data",[download length]);
    	
        [connection release];
        [download release];
    }
    
    @end
    
     
  20. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #20
    And it is practicly the same as mine. The only difference is that I am checking if connection == povezava. I tryed without it and then I don't get a memory leak. But I have to check. Because if I don't the wrong connection will get released (for now I only have one but I will have at least one more). That is one of the reasons that I went with the synchronous downloading - less complications.

    EDIT: Just tryed it again without the checking and for some reason tha app crashed three times. That didn't happen before. So no memory leak - application crashes or memory leak - application works. This is getting a little furstrating. With synchronous method at least the whole thing didn't have these problems.

    EDIT2: Maybe the problem is simulator specific. If I don't click the download button a few times in a second (as fast as I can) the whole memory leak doesn't appear. Probably because I can't create a new connection when the other one is running. Maybe I can solve this by disabling the downloaad button when a download is in progress (which is probably a good idea in any case).
     
  21. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #21
    Ah, yeah, causing multiple downloads was a little detail you left out of your explanation of how you were causing this memory leak. Next time try not to forget an important detail like that.

    Most definitely!
     
  22. r00li thread starter macrumors member

    Joined:
    Sep 27, 2008
    #22
    I disabled the button in the middle of download and no more memory leaks! Yes it is important detail but I didn't even think that that would cause a problem. Well it did. So now I can finaly get back to dealing with important things:D. And thank you for all the help!
     
  23. patrover macrumors newbie

    Joined:
    Aug 15, 2009
    #23
    1) dejo: aren't you releasing the connection twice? one when the connection finished loading and again then deallocating?

    2) i have the same problem, i don't have a button to disable and i'm freaking out. i have a memory leak but i'm releasing the connection both on
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    and
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

    what can be the reason?
     
  24. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #24
    Yeah, I guess so. I should probably set the connection to nil after releasing it to prevent issues during the dealloc. But over-releasing an object is not going to cause a memory leak. Quite the opposite in fact. Memory leaks are (usually?) caused by under-releasing an object.
     

Share This Page