6

All of my networking code relies on the NSURLSession delegate methods--rather than completion handlers. My data & download tasks all work great, but my upload tasks never result in URLSession:task:didCompleteWithError: being called. However, the URLSession:dataTask:didReceiveData: and URLSession:dataTask:willCacheResponse:completionHandler: delegate methods ARE called.

If I set the resource timeout to a really low value on my session object, then didCompleteWithErrors does get called, but that's clearly not a solution.

Any ideas? I'm about to go insane here.

Thanks.

Craig
  • 9,335
  • 2
  • 34
  • 38
mph
  • 808
  • 1
  • 10
  • 22
  • hi @mph how are you setting this task delegate ? Actually I am facing some issue and these delegates are not getting called. – user3300864 Sep 09 '16 at 07:12

5 Answers5

5

You will not see didCompleteWithError called if you implement willCacheResponse, but if the implementation fails to actually call the completionHandler. You must call the completionHandler.

The same is true with any and all of the various NSURLSession delegate methods that provide a completionHandler parameter (e.g. authentication challenges, redirects, etc.). If you implement these respective methods, you must ensure that the completionHandler is called.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    THANK YOU. I knew it had to be something trivial like this. – mph Nov 18 '14 at 05:09
  • After posting my question, I noticed that some of my data & download tasks were also suffering from the same problem. I guess short & sweet unit tests don't always suffer from the same problem, which actually makes it an even nastier and (seemingly) undocumented issue. ARGH. – mph Nov 18 '14 at 05:11
  • In defense of Apple's documentation, it says that, "If your delegate implements this method, it _must_ call this completion handler" (emphasis from original documentation). It doesn't accurately articulate what will happen if you don't call this `completionHandler` (worse, the docs describe that leaks will result, but neglect to acknowledge the broader problems that may arise), but in their defense, they are fairly explicit that the completion handler must be called. But I feel your pain: The only reason I know about this issue is that I once suffered through it myself. – Rob Nov 18 '14 at 14:05
3

Another possibility someone may want to check in this case, is that your URLSession delegate methods are not marked private. If they are, the methods will not be called... this can be easy to do by accident if you leave modifiers off the methods, then use auto-fix on a warning to fix the modifiers for the method (makes it private).

Method signature should be:

public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 
Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150
  • 1
    or "open func" – this bit me today, thanks for the "right" answer, even though the others are totally valid problems too. Down with delegate pattern! – snakeoil Nov 28 '18 at 19:34
  • @snakeoil Great point about open being valid, just something where it can be seen! Glad it could help as this had me puzzled for a while. – Kendall Helmstetter Gelner Nov 30 '18 at 04:55
  • 1
    WTF! This was it. The compiler didn't warn me that they need to be public. But strangely some of the delegate functions had been called. – heyfrank Jan 07 '21 at 10:25
2

Rob's answer is correct and gave me a big clue. However - for my specific problem - it didn't jump out at me why the NSURLSessionTaskDelegate methods might not be called. If I could be allowed to provide another answer with a different focus.

Many of the NSURLSession task creation methods have two variations; one with a completionHandler parameter and one without.

For example:

func dataTaskWithRequest(_ request: NSURLRequest) -> NSURLSessionDataTask

func dataTaskWithRequest(_ request: NSURLRequest, completionHandler completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask

If the completionHandler method is used, then any delegate methods - in this case for NSURLSessionTaskDelegate will be ignored. That's despite the fact that a delegate has been specified in the NSURLSession initialiser.

The following Playground code demonstrates this. Toggle the task declaration comments to see the delegate called or not:

import UIKit
import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true


class MyDelegate:NSObject, NSURLSessionTaskDelegate
{
    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
        NSLog("Delegate called")
    }
}


let myDel = MyDelegate()
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config, delegate: myDel, delegateQueue: NSOperationQueue())
let url = NSURL(string: "http://httpbin.org")
let request: NSURLRequest = NSURLRequest(URL: url!)

// With completionHandler, delegate method above NOT called
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
        NSLog("Task done")
})

// Without completionHandler, delegate method above IS called
//let task : NSURLSessionDataTask = session.dataTaskWithRequest(request)

task.resume()

TLDR: tearing your hair out wondering why the NSURLSession's delegate methods aren't called? User the alternative method signature to create your session tasks; that which omits the completionHandler parameter.

Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
  • The completions handler blocks don't have a reference to the `(NSURLSessionTask *)task` parameter used in the delegate methods. Without the `task` from the delegate, how can the task be retried from within the block? – pkamb Apr 04 '16 at 17:53
  • 1
    This answer *is* correct, by the way... ` `uploadTaskWithRequest:fromData:` hits the delegate methods, but `uploadTaskWithRequest:fromData:completionHandler:` does not. – pkamb Apr 04 '16 at 18:14
  • In my case, even though I'm NOT using completionHandler approach, delegate callbacks are not invoked. – mixtly87 May 23 '17 at 18:24
1

To expand upon another answer here, the NSURLSession docs state that either the completion handler or delegate methods will be called.

Like most networking APIs, the NSURLSession API is highly asynchronous. It returns data to your app in one of two ways, depending on the methods you call:

  • To a completion handler block that is called when a transfer finishes successfully or with an error.

  • By calling methods on the session’s delegate as data is received and when the transfer is complete.

A more specific ask of this question is here:

NSURLSession delegate and completionHandler

Community
  • 1
  • 1
pkamb
  • 33,281
  • 23
  • 160
  • 191
0

I solved it like this add completionHandler in willCacheResponse function if you have.

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *__nullable cachedResponse))completionHandler {

    NSLog(@"%s", __FUNCTION__);
    completionHandler(proposedResponse);
}
Meet Doshi
  • 4,241
  • 10
  • 40
  • 81