Need help understanding completionHandlers in delegate protocols

I've discovered several cases in NSURLSession with completionHandlers like this example:


       - (void) URLSession:(NSURLSession *)session
                      task:(NSURLSessionTask *)task
willperformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse 
                newRequest:(NSURLRequest *)request
         completionHandler:(void (^)(NSURLRequest * _Nullable))compHandler
{ // ... code to be handled when this occurs ...  }


and I am trying to understand how they are working.


I discovered the documentation saying for completionHandler

"A block that your handler should call with either the value of the

request
parameter, a modified URL request object, or
NULL
to refuse the redirect and return the body of the redirect response."

And in other cases it is must call. And, I discovered that to make the protocol method 'do something', (in this case, to follow the redirect) I had to add


compHandler(request);


Is this a block with no name, that has an NSURLRequest argument and returns nothing ? I am trying to understand what the 'compHandler' is, does and how this is block syntax.

Replies

So, the method you cited is part of the NSURLSessionTaskDelegate protocol.


I think I understand what you mean by "a block with no name", but, no, that's not right. It has a name, "compHandler".


The syntax for blocks is pretty strange. For parameters of Objective-C methods, in general, the syntax puts the type in parentheses and the name after the parentheses. For example, -initWithWidget:(Widget*)widget. The type is Widget*, the name is "widget".


You're probably familiar with the syntax for declaring a block variable:

returnType (^variableName)(parameterTypes);


As with other C declarations, the variable name is mixed in with the type. However, if you were to express just the type, not as a variable, it would be:

returnType (^)(parameterTypes)


This is the same as for C function pointer types, except with ^ instead of *.


So, now, for an Objective-C method parameter, you put the type in parentheses and the parameter name after the parentheses:

-(instancetype) initWithBlock:(returnType (^)(parameterTypes))parameterName;


You are correct that, for the delegate method in question, the block takes a single parameter which is a pointer to an NSURLRequest object and it returns nothing. It's typical that completion handlers would not return anything. It's a way for the asynchronous method — this delegate method — to pass control back to code of the caller's and there's no expectation of any information flow in the other direction.


So, somewhere in the NSURLSession implementation, it has received an HTTP redirection request. It is asking your object (its delegate) how to handle that redirection request. But it's not a synchronous query. It's asynchronous. NSURLSession is not blocked waiting for your method to provide a decision. It has moved on to do other things on other threads. Of course, it's not proceeding with this particular HTTP session because it can't without your delegate's decision, but it has other things it can attend to.


Anyway, when your code has made a decision, it has to pass that back to NSURLSession somehow. The way this API is designed is that NSURLSession has passed your code a block to execute such that the parameter indicates the decision. The block is some code of NSURLSession's. Exactly what it is is an implementation detail, but the general purpose is to resume processing of the HTTP session. If you pass the provided NSURLRequest object back, you've indicated that you want the redirection to proceed as normal (the request object was configured for the destination of the redirection). If you pass a different NSURLRequest back, you have overridden the redirection and the HTTP session will make your request to some URL of your choice. If you pass nil, you're preventing the redirection and the HTTP session will proceed with the original response (which will probably yield no useful content).


If you fail to invoke the completion handler at all, it's not clear what NSURLSession would do. It presumably would not proceed with the HTTP session at all. Hopefully, everything would be cleaned up properly.


If you don't want to do special handling of redirections, you can just not implement this method at all.

If you fail to invoke the completion handler at all, it's not clear what NSURLSession would do.

The NSURLSession task will simply stall. It can’t make any progress until you tell it how you want to handle the redirection, and it won’t time out because NSURLSession disables its timeout checking when it’s waiting for a delegate response like this. The task will stay stuck like this until you call the completion handler, or the session is invalidated, or your app terminates.

As a rule, for completion handlers like this, you want to make sure that you always call the block, and that you call it only once.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"