I am implementing a custom NSURLProtocol
, and internally want to use NSURLSession
with data tasks for internal networking instead of NSURLConnection
.
I have hit an interesting problem and wonder about the internal implementation of the challenge handler of NSURLSession
/NSURLSessionTask
.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
Here I am basically provided with two different challenge handlers, one being the completionHandler
block, which is provided with all necessary information to handle the challenge, but there is also the legacy NSURLAuthenticationChallenge.client
which has methods that correspond pretty much one to one with the completionHandler
information options.
Since I am developing a protocol, and would like to pass certain authentication challenges upward the URL loading system for the calling API to implement, I need to use the NSURLSession
client method:
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
My question is whether the internal implementation of the completionHandler
and NSURLAuthenticationChallenge.client
is the same, and if so, can I skip calling the completion handler in the delegate method, with expectance that the URL loading system will call the appropriate NSURLAuthenticationChallenge.client
method?
To answer my own question, the answer is no. Moreover, Apple's provided challenge sender does not implement the entire NSURLAuthenticationChallengeSender
protocol, thus crashing when client attempts to respond to challenge:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFURLSessionConnection performDefaultHandlingForAuthenticationChallenge:]: unrecognized selector sent to instance 0x7ff06d958410'
My solution was to create a wrapper:
@interface CPURLSessionChallengeSender : NSObject <NSURLAuthenticationChallengeSender>
- (instancetype)initWithSessionCompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
@end
@implementation CPURLSessionChallengeSender
{
void (^_sessionCompletionHandler)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential);
}
- (instancetype)initWithSessionCompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
self = [super init];
if(self)
{
_sessionCompletionHandler = [completionHandler copy];
}
return self;
}
- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, nil);
}
- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
_sessionCompletionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
- (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
@end
And I replace the challenge object with a new one, using my wrapped sender:
NSURLAuthenticationChallenge* challengeWrapper = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:[[CPURLSessionChallengeSender alloc] initWithSessionCompletionHandler:completionHandler]];
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challengeWrapper];