NSURLConnection Delegate method not being called

144 Views Asked by At

I'm new to Objective-C and I've been working on some old code trying to dynamically check if SSL-certificate errors should be ignored or not. I have already set a NSURLConnection delegate and its methods:

@interface Downloader : NSObject <NSURLConnectionDelegate>

-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:( NSURLAuthenticationChallenge *)challenge;

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

-(void)connectionDidFinishLoading:(NSURLConnection *)connection;

Implementation:

-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
    if([self.ignoreCertificateErrors isEqualToString:@"false"]){
        if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
    }
    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    _downloadError = error;
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [_downloadData appendData: data];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSError * error;
    [_downloadData writeToFile:_downloadDest options:NSDataWritingAtomic error:&error];
}

The problem I'm facing is that -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:( NSURLAuthenticationChallenge *)challenge is not being called before every download, so the property is not checked and the download happens regardless of the variable's value.

Does anyone know what could cause NSURLConnection to ignore its own delegate methods?

(Also, downloads happening asynchronously using NSRunLoop currentRunLoop

I know this is kind of an old issue, but non of the other answers have solved this problem for me.


This is an extract from the code that performs the asynchronous downloads:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t downloadQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
dispatch_async(downloadQueue, ^{
        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                                      delegate:self
                                                              startImmediately:NO];
        [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [connection start];
        [ [ NSRunLoop currentRunLoop ] run ];
        dispatch_semaphore_signal(semaphore);
        [connection release];
    });
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
4

There are 4 best solutions below

2
Ol Sen On

If a delegate is never called it is very likely that your responsible object does not exist at runtime. In this case a proper made delegate implementation would check if an object to call on exists, is of expected datatype and protocol as well checking if the selector method exists. When all of those are not your issues reason it may be because of missing or wrong parameter/object was given to the delegate objects selector or never called in time.

Often the delegate is just not set after allocation (of class using a delegate) or pointing at the wrong class type or just NULL and thats why never processed.

solution: Check if your id<DelegateProtocol> can point to an existing object at runtime and is configured to follow the correct protocol.

reminder: Implementation of Classname <ProtocolA, ProtocolB> helps you code all needed delegate methods and is publishing the method definitions in interface for you. If not - you should see any Xcode warning about it.

hint: in some cases even if documentation says there are defaults you need to set a delegate. And NSURLConnection is deprecated.

0
Rob On

You say that you’re initiating an asynchronous request, but that use of the semaphore is effectively taking an asynchronous API and making it behave synchronously. You should generally avoid using semaphores to make asynchronous methods synchronous unless absolutely necessary. It does not seem appropriate here. You're blocking whatever thread from which you called this. You can conceivably be deadlocking, too.

For what it’s worth, the proper technique of starting NSURLConnection on a background thread is complicated. For example, you're calling CFRunLoopStop on failure, but not on success. And the use of run is explicitly discouraged. As the documentation says:

If you want the run loop to terminate, you shouldn't use this [run] method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:

BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

where shouldKeepRunning is set to NO somewhere else in the program.

But before worrying about that, we should stop and ask why we are using this background thread at all. It’s much easier to just schedule the NSURLConnection on the main run loop. NSURLConnection is asynchronous (as long as you avoid using sendSynchronousRequest), so there's no need to dispatch the starting of these requests to a background queue. NSURLConnection won't block the main thread. (Needless to say, if you're doing any complicated processing in the NSURLConnection delegate methods, then you might dispatch that to some GCD queue, but the connection, itself, can just be added to the main runloop without incident.)

If you really want to use a background thread for the NSURLConnection, don't use semaphores. And use runMode:beforeDate: rather than run. Or, given that NSURLConnection is deprecated anyway, use NSURLSession, which can use background thread for its delegate without the runloop silliness.

0
dgatwood On

You should be using performDefaultHandlingForAuthenticationChallenge: here, not continueWithoutCredentialForAuthenticationChallenge:. Otherwise, you're breaking any keychain-based authentication, and probably lots of other stuff.

Also, you need to call setDelegateQueue: on the connection before starting the request. Otherwise, there's some possibility that you might be actively blocking the queue that the callbacks would be sent on.

Finally, you really shouldn't be ignoring certificate errors. Allowing a specific certificate with a known public key is okay, but ignoring certificate errors en masse is a really bad idea, even for internal builds, because those sorts of bits of code have a tendency to accidentally end up in production. :-)

0
SeaSpell On

Your dispatching onto the queue and then leaving the block. Once you leave the block connection is released and goes out of scope. Try declaring your connection before the block then wait then release.

        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                                  delegate:self
                                                          startImmediately:NO];

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
     dispatch_queue_t downloadQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
     dispatch_async(downloadQueue, ^{
    [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [connection start];
    [ [ NSRunLoop currentRunLoop ] run ];
    dispatch_semaphore_signal(semaphore);
});
 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        [connection release];