Husein Behboudi Rad
Husein Behboudi Rad

Reputation: 5462

NSURLConnection crashes after two times

I am use below address to download a mp3 file. I have a uitoolbarbutton and when this button tapped I call downloadandCreatePath and then it make a view that contains my`downloadProgressV' and a UIButton for cancelling the download.

I click download the view appears and download. Download done successfully and also if I cancel, it works fine too.

The problem is that if I do it for second time and then third time. The app crashes on the third time with EXC_BAD_ACCESS message on my appdelegate.

NSURLResponse *urlResponse;
NSMutableData *downloadedMutableData;
NSURLConnection *connectionManager;

- (NSString *) downloadandCreatePath: (int) doaId
{
    @try {

        self.downloadProgressV.progress=0.0;

        self.downloadView.hidden=NO;


        NSString *stringURL = @"http://www.ergmusic.com/mp3-samples/488000/488799.mp3";

        NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:stringURL]
                                                    cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                timeoutInterval:60.0];

        connectionManager = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];

        if (!connectionManager) {
            // Release the receivedData object.
            downloadedMutableData = nil;
            // Inform the user that the connection failed.
        }

    }
    @catch (NSException *exception) {
        NSLog(@"buuuuuuug");
        return @"";
    }
}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [downloadedMutableData setLength:0];
    urlResponse = response;
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [downloadedMutableData appendData:data];
    self.downloadProgressV.progress = ((100.0/urlResponse.expectedContentLength)*downloadedMutableData.length)/100;
}

- (void)connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)error
{
    connectionManager = nil;
    downloadedMutableData = nil;
}

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

    self.downloadProgressV.progress=0.0;
    self.downloadView.hidden = YES;
    connectionManager = nil;
    downloadedMutableData = nil;

}

- (IBAction)cancelDownloadTouchUpInside:(id)sender {
    self.downloadView.hidden=YES;
    [connectionManager cancel];
    connectionManager = nil;
    downloadedMutableData = nil;
}

Does any one knows where is my problem?

UPDATE

I try to debug the app with nszombies using below instruction:

http://michalstawarz.pl/2014/02/22/debug-exc_bad_access-nszombie-xcode-5/

but the error do not accourd in this time.

UPDATE

I instanitiate the NSMutableData in my viewdidload method.

Also I try to instantiate it on the first line of downloadandCreatePath but still I see the error.

UPDATE The error occurs after 2 time run and when I want to try initialize the connectionManager. I am wondering why after two time run and in the third try the problem occurs. Why it is not live after first run and in the second try?!!

update

I think below stack trace is better:

    <_NSCallStackArray 0x94ee270>(
0   ???                                 0x097c85cf 0x0 + 159155663,
1   MafatihTebyan                       0x00010cb4 -[DoaShowViewController downloadandCreatePath:] + 404,
2   MafatihTebyan                       0x000114be -[DoaShowViewController DisplayDoaPlayView:] + 94,
3   libobjc.A.dylib                     0x0181f874 -[NSObject performSelector:withObject:withObject:] + 77,
4   UIKit                               0x0057d0c2 -[UIApplication sendAction:to:from:forEvent:] + 108,
5   UIKit                               0x00851c9b -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 139,
6   libobjc.A.dylib                     0x0181f874 -[NSObject performSelector:withObject:withObject:] + 77,
7   UIKit                               0x0057d0c2 -[UIApplication sendAction:to:from:forEvent:] + 108,
8   UIKit                               0x0057d04e -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61,
9   UIKit                               0x006750c1 -[UIControl sendAction:to:forEvent:] + 66,
10  UIKit                               0x00675484 -[UIControl _sendActionsForEvents:withEvent:] + 577,
11  UIKit                               0x00674733 -[UIControl touchesEnded:withEvent:] + 641,
12  UIKit                               0x005ba51d -[UIWindow _sendTouchesForEvent:] + 852,
13  UIKit                               0x005bb184 -[UIWindow sendEvent:] + 1232,
14  UIKit                               0x0058ee86 -[UIApplication sendEvent:] + 242,
15  UIKit                               0x0057918f _UIApplicationHandleEventQueue + 11421,
16  CoreFoundation                      0x01a1383f __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15,
17  CoreFoundation                      0x01a131cb __CFRunLoopDoSources0 + 235,
18  CoreFoundation                      0x01a3029e __CFRunLoopRun + 910,
19  CoreFoundation                      0x01a2fac3 CFRunLoopRunSpecific + 467,
20  CoreFoundation                      0x01a2f8db CFRunLoopRunInMode + 123,
21  GraphicsServices                    0x0303b9e2 GSEventRunModal + 192,
22  GraphicsServices                    0x0303b809 GSEventRun + 104,
23  UIKit                               0x0057bd3b UIApplicationMain + 1225,
24  MafatihTebyan                       0x00008a2d main + 141,
25  libdyld.dylib                       0x03c1d725 start + 0,
26  ???                                 0x00000001 0x0 + 1
)

Important UPDATE

I noticed that the codes is not working on iOS 6 even at first call.In iOS 6 It keeps crashing all times.

UPDATE

All samples that I see for NSURLConnection are using viewdidload for creating request file. I am using it in a method and call this method again and again. because the url is changing at each call. May the problem about this?

Upvotes: 2

Views: 678

Answers (7)

Husein Behboudi Rad
Husein Behboudi Rad

Reputation: 5462

Thanks for all replies and helps.

Finally as @quellish and @Kuldeep said in their comments. I noticed that the problem is not related to the NSURLConnection. It was because of the view that I was showing the download progress view that I create. Please see the second line of my question.

When I do not use it and instead of this view used MBProgressHUD my problem has been fixed.

Thanks to all friends for their reply.

Upvotes: 0

quellish
quellish

Reputation: 21244

You have multiple connections reusing the same delegate. You may have cancelled a previous connection, but the delegate will still be getting messages for the previous connection. In each of your delegate messages, check the address of the passed in connection against that of [self connectionManager].

Example:

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    if (connection == [self connectionManager]){
        connectionManager = nil;
        downloadedMutableData = nil;
    }
}

You should also set the delegate of the connection to nil after canceling it or re-assigning it. You don't appear to be canceling previous connections. Example:

if ([self connectionManager] != nil){
    [[self connectionManager] cancel];
    /// Optional, if you are not interested in getting remaining delegate messages.
    [[self connectionManager] setDelegate:nil];
    [self setConnectionManager:nil];
}


connectionManager = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];

Though really, because you're re-using the delegate like this you should also think about how to handle when you are receiving messages for a connection other than the current connection. For example, if you cancel a connection the delegate may get a didFinishLoading or didFailWithError callback. This can also happen if you set a connection that's inflight to nil, as part of it's cleanup.

Upvotes: 0

Kuldeep
Kuldeep

Reputation: 2589

@Husein BehbudiRad

I would recommend you to use a well tested and published open source library for uploading or downloading the files to or from the server. I am talking about AFNetworking which you just need to use with below code snippet. It has all things managed in that as far as the performance, security, leaks, etc. are concern, all aspects managed already, so you need not to take any overhead about all those things.

You just need

  1. To download the open source code files from above link
  2. Integrate it to your project by drag and drop
  3. Copy and paste below function to your respective controller
  4. Pass the url in NSString format and file name in which you want to store it to documents directory to below function and you are good to go..

     -(BOOL)fileDownloadWithUrl:(NSString*)urlStr withFilename:(NSString*)fileName
     {
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *URL = [NSURL URLWithString:urlStr];
        NSURLRequest *request = [NSURLRequest requestWithURL:URL];
        AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
    
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:fileName];
        operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
    
        [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)     {
            NSLog(@"Successfully downloaded file to %@", path);
            return TRUE;
        }     failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"Error: %@", error);
            return FALSE;
        }];
    
        [operation start];
    }
    

I hope this will help you out.

Upvotes: 0

Sadiq Jaffer
Sadiq Jaffer

Reputation: 500

Don't set downloadedMutableData to nil.

Instead use

[downloadedMutableData resetBytesInRange:NSMakeRange(0, [downloadedMutableData length])];

Also to ensure that only one download task can be running at any given time, create a separate NSOperationQueue for your NSURLConnection tasks. Setting the maxConcurrentOperationCount property to one will create a FIFO queue for your downloads.

NSOperationQueue *queue=[[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount=1;
[queue addOperationWithBlock:^{
    YourDownloadCode...
}];

Upvotes: 0

Rob
Rob

Reputation: 437552

If you initiate a second download before the first is done, if you re-instantiate the NSMutableData (which you don't show us), the old NSMutableData will be released, resulting in the zombie-related error. You either need to maintain an array of NSMutableData objects or instantiate a new delegate object for each download.

Upvotes: 2

devops
devops

Reputation: 9179

you are destroying your downloadedMutableData object. Don't set it to nil, use setLength:0 instead.

Upvotes: 0

gro
gro

Reputation: 755

Not sure where you are init'ing downloadedMutableData. I have used the following in didRecieveData method:

    if (data==nil) { data = [[NSMutableData alloc] initWithCapacity:10240]; }

and then the appendData: line.

Upvotes: 0

Related Questions