How can I cancel an asynchronous call through NSURLConnection sendAsynchronousRequest?
Asked Answered
G

2

6

I've got a web service call performing some validation on user input in real time. I'd like to use [NSURLConnection sendAsynchronousRequest] on the validation (which was introduced in iOS 5), but cancel it if the user changes the input field content in the mean time. What is the best way to cancel a current request?

Gloaming answered 9/11, 2011 at 22:55 Comment(0)
G
19

It doesn't appear that there is a good way to do this. The solution seems to be to not use the new [NSURLConnection sendAsynchronousRequest] in situations in which you need to cancel the request.

Gloaming answered 10/11, 2011 at 17:54 Comment(1)
That's not correct; see my post below.Affer
B
3

I've managed to do this by placing the sendAsynchronousRequest method in a separate DownloadWrapper class, as follows:

//
//  DownloadWrapper.h
//
//  Created by Ahmed Khalaf on 16/12/11.
//  Copyright (c) 2011 arkuana. All rights reserved.
//

#import <Foundation/Foundation.h>

@protocol DownloadWrapperDelegate
- (void)receivedData:(NSData *)data;
- (void)emptyReply;
- (void)timedOut;
- (void)downloadError:(NSError *)error;
@end

@interface DownloadWrapper : NSObject {
    id<DownloadWrapperDelegate> delegate;
}
@property(nonatomic, retain) id<DownloadWrapperDelegate> delegate;
- (void)downloadContentsOfURL:(NSString *)urlString;
@end

@implementation DownloadWrapper
@synthesize delegate;

- (void)downloadContentsOfURL:(NSString *)urlString
{
    NSURL *url = [NSURL URLWithString:urlString];

    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:TIMEOUT_INTERVAL];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    [NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
    {
        if ([data length] > 0 && error == nil)
            [delegate receivedData:data];
        else if ([data length] == 0 && error == nil)
            [delegate emptyReply];
        else if (error != nil && error.code == ERROR_CODE_TIMEOUT)
            [delegate timedOut];
        else if (error != nil)
            [delegate downloadError:error];
    }];
}
@end

To utilise this class, I do the following, in addition to declaring the DownloadWrapper *downloadWrapper variable (in the interface declaration) and implementing the protocol methods which handles the response or a lack of one:

NSString *urlString = @"http://yoursite.com/page/to/download.html";
downloadWrapper = [DownloadWrapper alloc];
downloadWrapper.delegate = self;
[downloadWrapper downloadContentsOfURL:urlString];

Then I simply do the following to 'cancel' the connection when the view is about to disappear:

- (void)viewDidUnload
{
    [super viewDidUnload];
    downloadWrapper = nil;
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [downloadWrapper setDelegate:nil];
}

It's as simple as that. This would hopefully mimic the documented cancel method, which states that it does the following:

Once this method is called, the receiver’s delegate will no longer receive any messages for this NSURLConnection.

I was concerned that this (somewhat naive) method means that the packets of data would still come through in response to our URL request - only that we're no longer 'listening in' as the delegate. But then I realised that once the URL request was sent through, there's really no way of stopping the response from coming back to us - we can only disregard it (if not at this level, then still at some lower level in the network hierarchy). Please correct me if I'm wrong.

Either way, hope this helps.

Bookplate answered 16/12, 2011 at 23:16 Comment(2)
The one issue that I have with this solution is that you can only have one delegate at a time. One of the benefits of sendAsynchronousRequest:queue:completionHandler: is that you can bombard it with tons of requests from different objects. If you're wrapper delegate lives but the original request object doesn't not... crash. This was an issue for me when I tried to use this method in a global APIClient. So I agree with Micah, but this would be a viable solution for a simple implementation.Hiltonhilum
You're right, it was far too simplistic - even for what I needed it for. I've since changed my approach and utilised the RequestQueue class by Nick Lockwood instead. It has methods to cancel all (concurrent) requests or a particular one, if needed.Bookplate

© 2022 - 2024 — McMap. All rights reserved.