9

I have a strange problem when I try to invalidate an NSURLSession instance. The code is quite simple: I have a View Controller, two buttons (start: and stop:), and a text field for the url.

A simple extract of the code:

- (IBAction)start:(id)sender {
    NSURLSessionConfiguration *conf = [NSURLSessionConfiguration backgroundSessionConfiguration:@"conf"];
    self.session = [NSURLSession sessionWithConfiguration:conf delegate:self delegateQueue:nil];
    NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.url.text]]];
    [task resume];
}

- (IBAction)cancel:(id)sender {

    [self.session invalidateAndCancel];
}

or, if you prefer, the whole project: Link

Now, try to download a file ( http://download.thinkbroadband.com/1GB.zip ).

Since I want this download to continue in the background, I'm using a background session.
The session starts correctly and the download continue in the background, but if I try to cancel it (sending invalidateAndCancel) I have a bad access.
Profiling with Zombie enabled give this zombie object: _NSCFBackgroundDownloadTask.
So, if I retain the NSURLSessionDownloadTask (using a strong property to store it) the bad access doesn't happen. But, AFAIK, NSURLSession should retain it's tasks itself, so I would like to understand what's wrong with my code (maybe I'm missing something in the docs?) or if I should file a bugreport.

Thanks

LombaX
  • 17,265
  • 5
  • 52
  • 77

2 Answers2

17
  1. Use a better background session configuration identifier, please! This is not really your session; a background session is a kind of gateway into a shared system session. You need to distinguish your session's tasks from those of all the other apps doing background uploading and downloading. Use something unique, like @"com.company.appname.specialname".

  2. Canceling and invalidating a background session doesn't make much sense. You are killing the session; you'll never be able to use it again after invalidating it. That's silly. What you want to do is create a background session once, as your app launches, and just leave it there forever (as a gateway to the shared system session, as I said before). What you want to cancel, if you want to cancel something, is the task. Keep a reference to the task so you can say cancel to that reference, if you think you're going to want to cancel it. Or, if you really don't want to keep a reference to the task, you can ask the NSURLSession for a list of your current tasks by calling getTasksWithCompletionHandler:; a task can have an identifier, so there should be no problem finding the one you want and telling it to cancel.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks for the useful hints, however these are (good) suggestions about how to design the "downloader". Unfortunately they doesn't help with the bad access :-) So I can upvote but can't accept as answer. Thanks – LombaX Nov 17 '13 at 14:21
  • Thanks for the thanks. :) But seriously, I think you're wrong; the techniques I give will not bad-access, so the problem is solved. – matt Nov 17 '13 at 19:22
  • This is not an answer. "Do not use that API" does not resolve the problem of "the API crashes." – George Jul 02 '14 at 18:41
  • @George It is an answer. And in fact it was right as far as it went (it said true stuff, that would in fact have solved the practical problem). It just wasn't helpful enough to satisfy the OP! A somewhat inadequate answer is still an answer. A non answer is something like "I have the same problem!" – matt Jul 02 '14 at 20:00
  • 1
    If you create a session, then invalidate it, and then quickly recreate it, is this supposed to work? Or should problems be expected? My use case is in testing, where before a particular test case runs I want to make sure there are no pending background tasks still running. – Chris Prince Jan 06 '18 at 22:41
10

After debugging a lot I found that it happens when the following NSURLSessionTaskDelegate protocol method is not implemented by the delegate:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
}

Adding this method to the delegate solves the error. The "strange" thing is that the delegate method is marked as optional so It shouldn't be necessary. This makes me think that it's a bug, so I'll file a bugreport. I'll wait some days for more complete answers, then make mine as the correct one if no one appears.

EDIT:
if anyone is interested, I create an helper class-project to download files in background: Link To Project

LombaX
  • 17,265
  • 5
  • 52
  • 77
  • 2
    I had the same problem, a cryptic message about a BAD_EXEC, but Crashlytics gave me some light and helped me to search the root!! – Frederic Yesid Peña Sánchez Feb 26 '14 at 17:24
  • 1
    I've noticed something in my app. When doing rapid fire "invalidateAndCancel" followed by creating a new NSURLSession it seems to crash eventually if you don't respond to the -URLSession:didBecomeInvalidWithError: selector in your delegate. All in all, this and other issues I've faced lead me to believe that NSURLSession is not ready for prime time yet. – George Jul 02 '14 at 18:43
  • 1
    Thank you for this thread. It was the key to my finding a very obscure crash in an app I'm developing. Indeed it seems that on iOS 7, if you create a background upload task, you must either implement the `URLSession:task:didCompleteWithError:` delegate method, or keep a strong reference to your task, or you crash. – Duncan C Dec 04 '15 at 21:03