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

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

Related

iPhone app crashes randomly with exc_bad_access

I have an app with four tabs. In each tab I connect to remote server using nsurlconnection, fetches the response and display accordingly. While testing the app, I get crashes randomly. If I try to reproduce the crash again I do not get crash. I do not understand what is root cause of the crash. I enable NSZombie,symbolicated crash logs,checked the memory leak but no luck.
I started the project in Xcode 3 and now I imported same project to Xcode 4.2, so are there any issues with compatibility of Xcode?
And I use the same name for nsurlconnection in all tabs like
In Tab 1 I defined nsurlconnection as conn and Tab 2 defined nsurlconnection as conn.
Does this definition causes any issue?
Please help me solve this random crashes
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: url];
conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(label != nil){
progressView = [[ProgressView showHUDAddedTo:self.tabBarController.view animated:YES] retain];
progressView.labelText = label;
}
[request release];
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"didReceiveresponse");
if ([response isKindOfClass: [NSHTTPURLResponse class]]) {
if([(NSHTTPURLResponse *)response statusCode] == 200){
}
else{
//show Connection Error Alert
}
}
responseData = [[NSMutableData alloc]init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData");
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[progressView hide:YES];
NSLog(#"didFail");
//show failed alert
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"didfinish loading");
if([responseData length] > 0)
{
//handles response data
}
}
My guess without seeing code, would be that in a tab your making an NSURLConnection and doing something with the result when it completes. If you change tab before the result is returned then it is causing it to crash.
You need to cancel the NSURLConnection when viewDidDisappear, or make sure that whatever code is run on completion of it doesn't contain anything that would cause a crash if the tab isn't visible (like setting a labels text).
The way I handle this, is have a separate class that performs the URL requests that sends a notification when it's complete. That way in your viewDidAppear method you set your viewController to listen for the notifications, and in the viewDidDisapper method you stop listening for the notifications. So if your view isn't visible when the URL request finishes, the notification is fired but nothing happens.
Could you provide the output of the console? It seems not to be an error from Xcode.
These type of error usually appear when you try to access a deallocated object.
i am sure you have tried Instruments with memory leak. give a try with instruments with zombie tool, you can find it easily in instruments library.
run your code with this tool and if this crash is because of any zombie object then you will easily able to detect the location.
it has helped me few times.

iOS image resolution issue

In My project I am using image view which downloads image from server. It is working fine on iOS 4 but it is not showing on iOS 5.
Is there any minimum resolution needs to be take care while using iOS 5. One of image which comes from server is of 72 dpi resolution which works on iOS 4 but not on iOS 5.
I have written category to image view which will download code from image URL
Here is code snippet:
- (void) setImageFromServer:(NSString *) imageURL
{
if (imageURL!=nil)
{
ImageDownloader *imageDownloader = [[[ImageDownloader alloc] init] autorelease];
imageDownloader.requester = self;
[imageDownloader startDownload:imageURL];
}
}
- (void) didDownloadImageData:(NSData *) data forImageURL:(NSString *) imageURL
{
[self setImage:[UIImage imageWithData:data]];
}
In downloader file :
- (void) startDownload:(NSString *)MyimageURL {
self.imageData = [NSMutableData data];
self.currentImageURL = MyimageURL;
self.downloadConnection = [NSURLConnection connectionWithRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:self.currentImageURL]]
delegate: self];
[self.downloadConnection start];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[imageData appendData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self.requester didDownloadImageData:self.imageData forImageURL:self.currentImageURL];
isRewardTagImageAvailable = YES;
[connection release];
connection = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
}
I am not sure if this is a problem, but usually when I am writing a NSURLRequest I first initialize things as follows and the insert the request into a NSURLConnection. Sort of like this. Also note that once you initWithRequst in a NSURLConnection, you do not have to tell that connection to start, it will automatically.
NSURLRequest *tempReq = [[NSURLRequest alloc] initWithURL:someURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
NSURLConnection *tempCon = [[NSURLConnection alloc] initWithRequest:tempReq delegate:self];
I mean it should not matter, but give that a shot because looking at your, code it looks fine.
I would recommend adding the didFailWithError method:
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
To your files as well because maybe your connection is failing for some reason as well.
Is your connection synchronous or async ?
If async, you shouldn't autorelease your ImageDownloader, because there are some chance that it would be released for the connectionDidFinishLoading message.
Try to alloc your ImageDownloader normally, then release it when it finishes the download (both in connectionDidFinishLoading and didFailWithError)

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.

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?

Asynchronous NSURLConnection Throws EXC_BAD_ACCESS

I'm not really sure why my code is throwing a EXC_BAD_ACCESS, I have followed the guidelines in Apple's documentation:
-(void)getMessages:(NSString*)stream{
NSString* myURL = [NSString stringWithFormat:#"http://www.someurl.com"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:myURL]];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
receivedData = [[NSMutableData data] retain];
} else {
NSLog(#"Connection Failed!");
}
}
And my delegate methods
#pragma mark NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[receivedData release];
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
// release the connection, and the data object
[connection release];
[receivedData release];
}
I get an EXC_BAD_ACCESS on didReceiveData. Even if that method simply contains an NSLog, I get the error.
Note: receivedData is an NSMutableData* in my header file
Use NSZombieEnabled break point and check which is the freed object.
Also check:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if ([response expectedContentLength] < 0)
{
NSLog(#"Connection error");
//here cancel your connection.
[connection cancel];
return;
}
}
I have followed the guidelines in Apple's documentation:
That is not true. In both of the following, you break the rules:
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[receivedData release];
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
// release the connection, and the data object
[connection release];
[receivedData release];
}
In both cases, you do not obtain the connection object with alloc, a method beginning with new or containing copy. You do not own connection in these methods. You must not release it in these methods.
It seems to me slightly dodgy that you are releasing receivedData there too. I suggest you immediately set the instance variable to nil after you release it.
[receivedData release];
receivedData = nil;
That way, it won't get accidentally released moere than once.
If you're getting the error on didRecieveData regardless of the code inside it, it looks like your delegate has been freed?
I'd check that the object that contains the getMessages method isn't being released (or autoreleased) before the connection has dfinished getting data.
EDIT: The comments below show that my above answer is wrong :)
The problem was in the recievedData variable - it was being released early. Mark suggests releasing it in the dealloc method of the object that creates the connection so he deserves all the credit for this!
There's one slight thing to lookout for there - if you release the recievedData in the dealloc method, you will leak memory if you call getMessages more than once. You will need to change getMessages slightly to this :
...
if (theConnection) {
[recievedData release]; // If we've been here before, make sure it's freed.
receivedData = [[NSMutableData data] retain];
} else {
...
I got the same error when debugging with device although there was no problem in simulation. Adding the following line of code after releasing receivedData solved the problem:
receivedData = nil;
Commenting on JeremyP, where he says that "In both of the following, you break the rules": Sheehan Alam is following Apple's code (actually, cut'n'paste) found here.
I'd also like to add (and this is something that wasn't well answered here) that the 'build and analyze' flags a "potential leak" on the NSURLConnection (which is initiated with a "[NSURLConnection alloc]"). But if one puts in a [theConnection release] on the NSURLConnection, in the same method, it will crash.
So we have something that seems to defy the 'rules' for memory management, yet works (afaik) and is in Apple's documentation..
I got EXC_BAD_ACCESS on Asynchronous call at NSURLConnection.
The code is generated by http://www.sudzc.com
I needed to add a retain to
receivedData = [[NSMutableData data] retain];
and the callback methods doesn't get bad access signal anymore.
if I add the
if ([response expectedContentLength] < 0)
{
NSLog(#"Connection error");
//here cancel your connection.
[connection cancel];
return;
}
than all my webservices are canceled, otherwise works perfectly.
While it doesn't answer the full question, I've run into this error a couple of times because I set the request's HTTPBody to an NSString instead of a NSData. Xcode tried to warn me.