Reputation: 5462
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
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
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
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
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
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
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
Reputation: 9179
you are destroying your downloadedMutableData object. Don't set it to nil, use setLength:0 instead.
Upvotes: 0
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