20

I'm using an NSURLSession object to load images in my application. That could be loading several images simultaneously.

In some moments I need to cancel the loading of one specific image and continue loading others.

Could you suggest the correct way to do that?

Avt
  • 16,927
  • 4
  • 52
  • 72
Rostyslav Druzhchenko
  • 3,673
  • 3
  • 33
  • 38
  • 1
    Well, there is the `NSURLSessionTask` API, find what you want to cancel, and call `cancel` on it. – esh May 07 '14 at 13:13
  • Correct, it's `NSURLSesstionTask` and it's easy to send it `cancel` method. But the problem is to find a specific task. In this case I need to list all tasks of current session and find the task that I need by description, for example. So, I need a way to list tasks in the session. – Rostyslav Druzhchenko May 07 '14 at 13:20

6 Answers6

30

To get tasks list you can use NSURLSession's method

- (void)getTasksWithCompletionHandler:(void (^)(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks))completionHandler;

Asynchronously calls a completion callback with all outstanding data, upload, and download tasks in a session.

Then check task.originalRequest.URL for returned tasks to find the one you want to cancel.

Avt
  • 16,927
  • 4
  • 52
  • 72
  • You could look into `Obtaining General Task Information` section in [this](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionTask_class/Reference/Reference.html#//apple_ref/doc/uid/TP40013436-CH1-SW14). And then identify your request through these means and cancel the task in the block included in this method. – esh May 08 '14 at 05:28
  • It's weird, on Xcode 6 and iOS 8 and Swift, I get a "selector not recognized" when I call invalidateAndCancel() on a dataTask object. – Van Du Tran Nov 27 '14 at 19:19
  • 1
    @VanDuTran You should use 'cancel' method https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionTask_class/index.html#//apple_ref/occ/instm/NSURLSessionTask/cancel – Avt Dec 02 '14 at 12:36
11

Based on all the answers below, I'd go for something like this:

Swift 5

 func cancelTaskWithUrl(_ url: URL) {
    URLSession.shared.getAllTasks { tasks in
      tasks
        .filter { $0.state == .running }
        .filter { $0.originalRequest?.url == url }.first?
        .cancel()
    }
  }

You also probably want to account for your task completion handler, since canceling the task will result Error in that completion handler.

inokey
  • 5,434
  • 4
  • 21
  • 33
  • 5
    Thanks for your suggestion @inokey. `.filter { $0.state == .running }` can be omitted. Since the documentation says `The arrays passed to the completion handler contain any tasks that you have created within the session, not including any tasks that have been invalidated after completing, failing, or being cancelled.` – Rostyslav Druzhchenko Oct 08 '19 at 12:28
6

Hope below code help.

-(IBAction)cancelUpload:(id)sender {    
    if (_uploadTask.state == NSURLSessionTaskStateRunning) {
        [_uploadTask cancel];
     }
  }
byJeevan
  • 3,728
  • 3
  • 37
  • 60
annu
  • 137
  • 4
  • 12
5

Swift 3.0 version of @Avt's answer to get the task list. Use getTasksWithCompletionHandler.

func getTasksWithCompletionHandler(_ completionHandler: @escaping ([URLSessionDataTask], 
   [URLSessionUploadTask], 
   [URLSessionDownloadTask]) -> Void) {     
}

The returned arrays contain any tasks that you have created within the session, not including any tasks that have been invalidated after completing, failing, or being cancelled.

Jimbo
  • 25,790
  • 15
  • 86
  • 131
pedrouan
  • 12,762
  • 3
  • 58
  • 74
  • I've upvoted this so hopefully it goes to the top, as this is the correct was of doing it without keeping a manual list yourself. – Jimbo Feb 21 '18 at 22:06
1

I suggest two methods:

  1. Put the list of NSURLSessionTask in an array. In case you don't know exactly how many images you would get. Though you have to know the index of session in order to cancel it.
  2. If you get a limited number of images. Just use a set of NSURLSessionTask as global variables so you can access to cancel it anywhere in your class.
Thanh-Nhon Nguyen
  • 3,402
  • 3
  • 28
  • 41
  • 1
    Thanks for your answer but I believe that are little bit rude ways. Both of them will work but both of them have software design problems such as using global variables and duplicating. It's possible to use them if there are no correct way exists. – Rostyslav Druzhchenko May 07 '14 at 14:04
1

I think you should do this...

First, keep track of your requests per xib

var download_requests = [NSURLSession]()

Then, whenever you make a request, append your request to your array like so,

let s = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
if let url = NSURL(string: "http://my.url.request.com")
{
  download_requests.append(s)
  s.dataTaskWithURL(url)
  { (data, resp, error) -> Void in
    // ....
  }
}

Then whenever you want to cancel any outstanding requests, (let's say on viewDidDisappear), do

  override func viewDidDisappear(animated: Bool)
  {
    super.viewDidDisappear(animated)
    //stop all download requests
    for request in download_requests
    {
      request.invalidateAndCancel()
    }
  }