2

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?

Husein Behboudi Rad
  • 5,434
  • 11
  • 57
  • 115
  • can you add the stack trace when it crashed? – 3329 Jun 16 '14 at 12:29
  • How can I find the stack trace? Can you advise me about this? – Husein Behboudi Rad Jun 16 '14 at 15:50
  • If you can make it crash on Xcode, type `po [NSThread callStackSymbols]` in console when it crashes. Or `bt all` might be better. – 3329 Jun 16 '14 at 16:05
  • All I can think of is a concurrency problem. The problem may occurs from `connectionManager` or `urlResponse` or `downloadMutableData` by setting new values into them concurrently. That way `autorelease` will be called 2 times on the old value and cause app to crash as in your stack trace. Try to check if this situation is possible or not. Or try add `@synchronized(self)` wrapping every method you showed here. – 3329 Jun 16 '14 at 17:45
  • 1
    It does not look like a concurrency problem, it appears that the connection and callbacks are all being done on the main thread. The autorelease pool is drained when the runloop turns over, and if this were a concurrency problem there would not be an autorelease pool on any of the new threads unless it was explicitly created for each. – quellish Jun 17 '14 at 19:01
  • I noticed that at first and second call of the 'downloadandCreatePath' the 'urlString' method is 'nil' but at third load it has an address.same thing for 'urlRequest'. Anyway, when I assign them value the app do not crash but when I try to initialize the 'nsurlconnection' then app crash. – Husein Behboudi Rad Jun 17 '14 at 19:25

7 Answers7

2

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

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.

gro
  • 755
  • 4
  • 7
0

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

alex
  • 8,904
  • 6
  • 49
  • 75
0

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...
}];
Sadiq Jaffer
  • 500
  • 2
  • 10
0

@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.

Kuldeep
  • 2,589
  • 1
  • 18
  • 28
  • 1
    I am familiar with this third party libraries but now we are talking about NSURLConnection. I am wondering how can we fix this problem – Husein Behboudi Rad Jun 20 '14 at 09:03
  • 1
    See this problem of EXC_BAD_ACCESS you are facing due to multiple threads accessing same variable or function. Wherein in this library they have achieved thread safety of the functions through blocks. And main thing you will get the response inline in the completion block itself. Please let me know what problem you are facing while using this.function – Kuldeep Jun 20 '14 at 10:48
  • 1
    @HuseinBehbudiRad For more reference you can refer another stack answer : http://stackoverflow.com/questions/8372661/how-to-download-a-file-and-save-it-to-the-documents-directory-with-afnetworking – Kuldeep Jun 20 '14 at 11:23
  • 1
    @Kuldeep If you look at his stack trace, his problem isn't related to threading. All of this is occurring on the same thread. – quellish Jun 22 '14 at 04:14
  • @quellish Agree. But this was the suggestion to take up and use another approach which is more safe and suitable than this. – Kuldeep Jun 23 '14 at 06:12
  • I don't agree that AFNetworking is a good solution. Among other things, IIRC, it uses synchronous requests, which leak memory, and uses NSOperation queues to "cancel" those requests (which really means that they run until finished or they fail, but the result is ignored). Ugh. To track connection data, just put a dictionary in the delegate, use [connection hash] as the key, and use a dictionary as the value. Store your incoming data in that dictionary, along with any other info you might need to track. Store the active connections in an array and remove after the connection completes. – dgatwood Jun 11 '15 at 16:36
0

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.

quellish
  • 21,123
  • 4
  • 76
  • 83
0

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.

Husein Behboudi Rad
  • 5,434
  • 11
  • 57
  • 115