I have a subclass:
#import <Foundation/Foundation.h>
#interface CustomURLConnection : NSURLConnection <NSURLConnectionDelegate>
#end
In it's implementation file i have the following function:
-(void) connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"Authenticating from subclass.");
}
note that didReceiveAuthenticationChallenge is part of NSURLConnectionDelegate
This snippet is currently in every class that sends a NSURLRequest using NSURLConnection:
-(void) connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
// Code to authenticate ourselves.
}
The actual problem:
I want the subclass to have a predetermined behaviour for
connection:(CustomURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
instead of having to implement the function in every class. And instead have every class use the subclass instead and have every authentication challenge handled automatically by the subclass.
The class is allocated as follows:
cUrlConnection = [[CustomURLConnection alloc]initWithRequest:req delegate:self startImmediately: YES];
if (cUrlConnection)
{
// Handle events when connection is active.
}
If anyone has any insight in how i make my CustomURLConnection handle the authentication mechanism and/or tips/pointers i'd be delighted.
It's a somewhat awkward pattern, but what you want to do in the subclass is take over the delegate setting/getting and the provide a different, private-implementation delegate to the superclass that implements this method the way you want and passes all the other methods to the user-supplied delegate. (Note: since NSURLConnection takes its delegate at init-time, there's no -delegate and -setDelegate: methods to override. This is untested, but it might look something like this:
#interface MyCustomURLConnection : NSURLConnection
#end
#interface MyPrivateImpDelegate : NSObject
- (id)initWithRealDelegate: (id<NSURLConnectionDelegate>)realDel;
#end
#implementation MyPrivateImpDelegate
{
__weak id<NSURLConnectionDelegate> _userDelegate;
}
- (id)initWithRealDelegate:(id<NSURLConnectionDelegate>)realDel
{
if (self = [super init])
{
_userDelegate = realDel;
}
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
// Your auth implementation here...
// maybe call through if you want?
if ([_userDelegate respondsToSelector: _cmd])
{
[_userDelegate connection:connection didReceiveAuthenticationChallenge:challenge];
}
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return _userDelegate;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL retVal = [super respondsToSelector: aSelector];
if (!retVal)
{
retVal = [_userDelegate respondsToSelector: aSelector];
}
return retVal;
}
#end
#implementation MyCustomURLConnection
{
MyPrivateImpDelegate* _fakeDelegate;
}
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately
{
MyPrivateImpDelegate* privDel = [[MyPrivateImpDelegate alloc] initWithRealDelegate: delegate];
if (self = [super initWithRequest:request delegate:privDel startImmediately:startImmediately])
{
_fakeDelegate = privDel; // make sure our private delegate lives as long as we do.
}
return self;
}
- (void)dealloc
{
_fakeDelegate = nil;
}
#end
Related
I'm currently trying to set up a custom delegate on an async dataTaskWithRequest via NSURLSession. I've set up the protocol and implemented the delegate method, but I am stuck at figuring out whether I've implemented it correctly, and how to unit test it. Specifically, I'd like to test if the delegate returns something after it has been called, and test with a live API call. I've tried testing via the approach suggested here (OCUnit test for protocols/callbacks/delegate in Objective-C), but the test fails, probably because I'm missing something or am not taking into account the async call. Code of attempted delegate implementation and unit test are below.
Delegate protocol declaration:
#include "PtvApiPublic.h"
#ifndef PtvApiDelegate_h
#define PtvApiDelegate_h
#class PtvApi;
#protocol PtvApiDelegate <NSObject>
-(void) ptvApiHealthCheck: (PtvApi *) sender;
#end
#endif /* PtvApiDelegate_h */
Header file:
#include "PtvApiDelegate.h"
#ifndef PtvApi_h
#define PtvApi_h
#interface PtvApi : NSObject
#property (nonatomic, weak) id <NSURLSessionDelegate> delegate;
- (void)ptvApiHealthCheck;
#end
#endif /* PtvApi_h */
Snippet of PtvApi.m
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonHMAC.h>
#import <CommonCrypto/CommonDigest.h>
#import "PtvApiPublic.h"
#import "PtvApiPrivate.h"
#import "PtvApiDelegate.h"
#implementation PtvApi
#synthesize delegate;
...
- (void)ptvApiHealthCheck
{
NSString *fullUrl = [self GenerateRequestUrl];
NSURLSession *apiSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:delegate delegateQueue:nil];
NSURL *apiUrl = [NSURL URLWithString: fullUrl];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:apiUrl];
[apiSession dataTaskWithRequest:urlRequest];
}
#end
Unit test:
#import <XCTest/XCTest.h>
#import "PtvApiPublic.h"
#interface APIDelegateTests : XCTestCase <NSURLSessionDelegate>
{
PtvApi *testApi;
BOOL callbackInvoked;
}
#end
#implementation APIDelegateTests
- (void)setUp {
[super setUp];
testApi = [[PtvApi alloc] init];
testApi.delegate = self;
}
- (void)tearDown {
testApi.delegate = nil;
[super tearDown];
}
- (void)testThatApiCallbackWorks {
[testApi ptvApiHealthCheck];
XCTAssert(callbackInvoked, #"Delegate should return something, I think...");
}
#end
Alright I figured it out myself with the help of Apple's documentation for NSURLSession and the blog at http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/. I've tested it with the live URL for now, I'll update the answer when I add API mocking to my unit test and make the unit test more robust.
The short of it is that NSURLSession has its own delegate methods, and in this case where dataTaskWithRequest is used, an NSURLSessionDataDelegate can be set up and used to retrieve the API results.
The code for the delegate declaration is mostly correct, I just needed to change NSURLSessionDelegate to NSURLSessionDataDelegate in the header file.
The unit test requires a bit of set up, but is otherwise pretty straightforward. It involves initialising the class with the NSURLSession call, setting the object's delegate to self, and initialise a flag variable to NO. The flag variable will be set to YES when the delegate is called, which is what I am initially testing for. The fully set up unit test is below.
#interface APIDelegateTests : XCTestCase <NSURLSessionDataDelegate>
{
PtvApi *testApi;
BOOL callbackInvoked;
}
#end
#implementation APIDelegateTests
- (void)setUp {
[super setUp];
testApi = [[PtvApi alloc] init];
testApi.delegate = self;
callbackInvoked = NO;
}
- (void)tearDown {
testApi.delegate = nil;
[super tearDown];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
callbackInvoked = YES;
}
// Method is credit to Claus Brooch.
// Retrieved from http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/ on 10/04/2016
- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if([timeoutDate timeIntervalSinceNow] < 0.0)
break;
} while (!callbackInvoked);
return callbackInvoked;
}
- (void)testThatApiCallbackWorks {
[testApi ptvApiHealthCheck];
XCTAssert([self waitForCompletion:30.0], #"Testing to see what happens here...");
}
#end
I have created a class that will collect data from url data asynchronously, however my understanding of callbacks or whatever is not clear and I'm trying to find a simple way to reuse my class by having the calling method wait for data to be returned or set within the ApiManager class. I just need something to wakeup in another class when that process has been completed. Some processes have single request and others have multiple, why you will notice that I'm using [connection description] within the ApiManager class.
ApiManager.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface ApiManager : NSObject<NSURLConnectionDelegate>
{
NSMutableDictionary *_dataDictionary;
}
- (void)urlRequest:(NSURLRequest *)url;
#property (strong, nonatomic) NSMutableArray *results;
#end
ApiManager.m
#import "ApiManager.h"
#implementation ApiManager
- (void)urlRequest:(NSURLRequest *)url {
[[NSURLConnection alloc] initWithRequest:url delegate:self];
}
// basic connection classes
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSMutableData *responceOjb = _dataDictionary[ [connection description] ];
[_dataDictionary setObject:responceOjb forKey:[connection description]];
}
// append any data we find
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSMutableData *responceOjb = _dataDictionary[ [connection description] ];
[responceOjb appendData: data];
[_dataDictionary setObject:responceOjb forKey:[connection description]];
}
// --
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
}
// wrap up and close the connect, move objects over to results or something
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[_results addObject:[connection description]];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// The request has failed for some reason!
// Check the error var
NSLog(#"%#",error);
}
#end
The main View Controller test:
#import "ViewController.h"
#import "ApiManager.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self DoThisTest];
}
-(void)DoThisTest {
ApiManager *api = [[ApiManager alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",#"http://google.com"]]];
[api urlRequest:request];
if([api results]) {
NSLog(#"GOT DATA");
}
}
Well, there are a few options. You could add a block property onto your ApiManager class:
#property (copy, nonatomic) void (^doneHandler)();
And then invoke that block like so:
self.doneHandler();
You would invoke the block when you deem it appropriate (say, in your connectionDidFinishLoading: method).
With this approach, the definition of the block (callback) would happen in your view controller and look something like:
ApiManager *apiManager = [[ApiManager alloc] init];
apiManager.doneHandler = ^{
// Do whatever you need to do here.
};
Alternatively, you could add a method to your ApiManager with a signature like this:
- (void)sendRequestWithURL:(NSURL*)url completion:(void(^)())completion;
And use NSURLConnection's (or, better, NSURLSession's) block-based APIs. Those APIs have callbacks built in and you would simply invoke completion(); inside of the completion block of -[NSURLSession sendAsynchronousRequest:completion:].
Finally, you could define an ApiManagerDelegate protocol.
- (void)apiManagerDidFinishReceivingData:(ApiManager*)sender;
And add a delegate property to your ApiManager class.
#property (weak, nonatomic) id<ApiManagerDelegate>delegate;
Assign the delegate of your ApiManager in your ViewController:
ApiManager *apiManager = [[ApiManager alloc] init];
apiManager.delegate = self;
Call the delegate method inside of your implementation of NSURLConnectionDelegate's callbacks in ApiManager like so:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[_results addObject:[connection description]];
[self.delegate apiManagerDidFinishReceivingData:self];
}
And implement the delegate method in ViewController:
- (void)apiManagerDidFinishReceivingData:(ApiManager*)sender {
// Do what you want to.
}
As an addendum, there are networking libraries available that do a lot of the heavy lifting and busy-work for you, most notably AFNetworking, if you're just trying to get stuff done. And, even if this is more of an academic exercise where you're trying to understand the patterns, looking at AFNetworking's APIs and implementation (it's open source) would be highly instructive.
Cheers
I want to log all the NSURLRequests which are initiated from within my app and response for those requests. I wrote a custom NSURLProtocol to achieve this. I was able to trap all the request but not the response.
In canInitWithRequest method I can log all the requests regardless of method returns YES/NO.
Right now I am returning YES in hope the of actual response.
In -startLoading method I am supposed to inform NSURLProtocolClient with response and progress. I am not interested in modifying/creating my own response instead I am interested in actual response and want to log it. When and where would I find actual response for the request?
I am not interested in modifying URL loading behavior.
Am I on the right track with custom protocol or is there something else I need to do to log all the requests and responses?
#implementation TestURLProtocol
+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
//here i can log all the requests
NSLog(#"TestURLProtocol : canInitWithRequest");
return YES;
}
+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
-(void)startLoading
{
// I don't want to create or modify response.
//I have nothing todo with response instead I need actual response for logging purpose.
NSURLResponse * response = [[NSURLResponse alloc] initWithURL:[self.request URL] MIMEType:#"text/html" expectedContentLength:-1 textEncodingName:#"utf8"];
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocolDidFinishLoading:self];
}
-(void)stopLoading
{
}
#end
Well, I dropped the idea of custom NSURLProtocol. I wrote a class which confirms to NSURLConnectionDelegate, NSURLConnectionDataDelegate etc as per your need. This class serves as a common delegate to all NSURLConnection instances. It has a property of type id(depend upon requirements), this property holds the object which is creating NSURLConnection and interested in delegate callbacks as well.
URLRequestLogger instance serves as a common delegate to all NSURLConnection instances.
Each NSURLConnection has a URLRequestLogger instance as a delegate.
#import <Foundation/Foundation.h>
#interface URLRequestLogger : NSObject<NSURLConnectionDataDelegate>
-(id)initWithConnectionDelegate:(id<NSURLConnectionDataDelegate>)connectionDelegate;
#end
implementation file
#import "URLRequestLogger.h"
#interface URLRequestLogger ()
#property(nonatomic, assign) id<NSURLConnectionDataDelegate> connectionDelegate;
#end
#implementation URLRequestLogger
-(id)initWithConnectionDelegate:(id<NSURLConnectionDataDelegate>)connectionDelegate
{
self = [super init];
if (self)
{
_connectionDelegate = connectionDelegate;
}
return self;
}
//one can implement all the delegates related to NSURLConnection as per need
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//log reponse info
if ([response isKindOfClass:[NSHTTPURLResponse class]])
{
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
NSDictionary * responseHeaders = [httpResponse allHeaderFields];
NSInteger statusCode = [httpResponse statusCode];
//save as many info as we can
}
//if connectionDelegate is interested in it, inform it
if ([self.connectionDelegate respondsToSelector:#selector(connection:didReceiveResponse:)])
{
[self.connectionDelegate connection:connection didReceiveResponse:response];
}
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//log the error and other info
//if connectionDelegate is interested in it, inform it
if ([self.connectionDelegate respondsToSelector:#selector(connection:didFailWithError:)])
{
[self.connectionDelegate connection:connection didFailWithError:error];
}
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//log the connection finish info
//response time and download time etc
//if connectionDelegate is interested in it, inform it
if ([self.connectionDelegate respondsToSelector:#selector(connectionDidFinishLoading:)])
{
[self.connectionDelegate connectionDidFinishLoading:connection];
}
}
#end
MyViewController creates an NSURLConnection and is interested in delegate methods as well. At the same time we want to log all the details about request and response.
#import <UIKit/UIKit.h>
#interface MyViewController : UIViewController<NSURLConnectionDataDelegate>
#end
implementation file
#import "MyViewController.h"
#import "URLRequestLogger.h"
#implementation MyViewController
//MyViewController class creates a request and interested in connection delegate callbacks
//MyViewController confirms to NSURLConnectionDelegate.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self sendRequest];
}
-(void)sendRequest
{
/*
connection delegate here would be our URLRequestLogger class instance which holds MyViewController instance
to return the callbacks here after logging operations are finished
*/
URLRequestLogger * requestLogger = [[URLRequestLogger alloc] initWithConnectionDelegate:self];
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.example.org"]] delegate:requestLogger];
}
#pragma mark - NSURLConnection delegate methods
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//this callback is received from URLRequestLogger after logging operation is completed
//do something. Update UI etc...
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//this callback is received from URLRequestLogger after logging operation is completed
//do something. Update UI etc...
}
#end
Better solution with custom NSURLProtocol:
One can log all outgoing URL request and alter request properties, response and do other things like loading encrypted html files from disk into webview etc.
.h file
#import <Foundation/Foundation.h>
#interface URLRequestLoggerProtocol : NSURLProtocol
#end
.m file
#import "URLRequestLoggerProtocol.h"
#interface URLRequestLoggerProtocol ()<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
#property(nonatomic, strong) NSURLConnection * connection;
#end
#implementation URLRequestLoggerProtocol
#synthesize connection;
+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
//logging only HTTP and HTTPS requests which are not being logged
NSString * urlScheme = request.URL.scheme;
if (([urlScheme isEqualToString:#"http"] || [urlScheme isEqualToString:#"https"]) && ![[NSURLProtocol propertyForKey:#"isBeingLogged" inRequest:request] boolValue])
{
return YES;
}
return NO;
}
+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
-(void)startLoading
{
NSMutableURLRequest * newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:[NSNumber numberWithBool:YES] forKey:#"isBeingLogged" inRequest:newRequest];
self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];
}
-(void)stopLoading
{
[self.connection cancel];
}
#pragma mark - NSURLConnectionDelegate methods
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//log all things along with error and finally inform URL client that you are done
[self.client URLProtocol:self didFailWithError:error];
}
#pragma mark - NSURLConnectionDataDelegate methods
-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
//start logging a request properties from here
return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//log the response and other things and inform client that you are done.
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//log the data and other things and inform client that you are done.
[self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//log the finish info and other things and inform client that you are done.
[self.client URLProtocolDidFinishLoading:self];
}
#end
I subclassed a NSURLConnection in order to call web services using ssl.
I thought about doing it synchronous and not only asynchronous connection as it's now.
My code:
#import "SecureSSLConnection.h"
static NSMutableArray *sharedConnectionList = nil;
#implementation SecureSSLConnection
#synthesize request, completionBlock, internalConnection;
- (id)initWithRequest:(NSMutableURLRequest *)req
{
self = [super init];
if (self) {
self.request = req;
}
return self;
}
- (void)start
{
container = [NSMutableData new];
internalConnection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES];
if (!sharedConnectionList) {
sharedConnectionList = [NSMutableArray new];
}
[sharedConnectionList addObject:self];
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[container appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.completionBlock) {
self.completionBlock(container, nil);
}
[sharedConnectionList removeObject:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if (self.completionBlock) {
self.completionBlock(nil, error);
}
[sharedConnectionList removeObject:self];
}
#pragma mark - SSL Connection Addition
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"challenge.protectionSpace.host: %#", challenge.protectionSpace.host);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// We only trust our own domain
if ([challenge.protectionSpace.host isEqualToString:WebSiteURL]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
I started thinking about implementing:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
In order to keep it the same as original delegate.
One way is using NSNotificationCenter but my goal is much easier method for it.
Any ideas how should I doing it?
I would not suggest making this synchronous. You invariably want to keep your network requests asynchronous.
The simple solution is to employ your completion block property. Simply put other tasks that are dependent upon your initial SSL request inside the completion block of the first request. That way, they won't start until the first completes.
The more elegant solution is to take this a step further, and wrap your SSL request in a concurrent NSOperation subclass. That way, you can employ not only the above completion block pattern, but you can use the addDependency of subsequent operations and maxConcurrentOperationCount of the NSOperationQueue to really refine the dynamics between various operations. This is non-trivial, but you can see AFNetworking for an example of a relatively well thought-out implementation of this pattern.
I'm creating an app which communicates alot with a server. I want to make sure I have only 1 connection at a time - only 1 request pending. I want so that if I try to send another request, It will wait until the current one is finished before sending the next.
How can I implement This?
Tnx!
I don't know of any automatic mechanism to do this. So you have to write a new class yourself (let's call it ConnectionQueue):
Basically, instead of a creating the NSURLConnection directly, you call a method of your ConnectionQueue class (which should have exactly one instance) taking the NSURLRequest and the delegate as a parameter. Both are added to a queue, i.e. a separate NSArray for the requests and the delegates.
If the arrays contains just one element, then no request is outstanding and you can create a NSURLConnection with the specified request. However, instead of the delegate passed to the method, you pass your ConnectionQueue instance as the delegate. As a result, the connection queue will be informed about all actions of the connection. In most cases, you just forward the callback to the original delegate (you'll find it in the first element of the delegate array).
However, if the outstanding connection completes (connection:didFailWithError: or connectionDidFinishLoading: is called), you first call the original delegate and then remove the connection from the two arrays. Finally, if the arrays aren't empty, you start the next connection.
Update:
Here's some code. It compiles but hasn't been tested otherwise. Furthermore, the implementation of the NSURLConnectionDelegate protocol is incomplete. If you're expecting more than implemented callbacks, you'll have to add them.
Header File:
#import <Foundation/Foundation.h>
#interface ConnectionQueue : NSObject {
NSMutableArray *requestQueue;
NSMutableArray *delegateQueue;
NSURLConnection *currentConnection;
}
// Singleton instance
+ (ConnectionQueue *)sharedInstance;
// Cleanup and release queue
+ (void)releaseShared;
// Queue a new connection
- (void)queueRequest:(NSURLRequest *)request delegate:(id)delegate;
#end
Implementation:
#import "ConnectionQueue.h"
#implementation ConnectionQueue
static ConnectionQueue *sharedInstance = nil;
+ (ConnectionQueue*)sharedInstance
{
if (sharedInstance == nil)
sharedInstance = [[ConnectionQueue alloc] init];
return sharedInstance;
}
+ (void)releaseShared
{
[sharedInstance release];
sharedInstance = nil;
}
- (id)init
{
if ((self = [super init])) {
requestQueue = [NSMutableArray arrayWithCapacity:8];
delegateQueue = [NSMutableArray arrayWithCapacity:8];
}
return self;
}
- (void)dealloc
{
[requestQueue release];
[delegateQueue release];
[currentConnection cancel];
[currentConnection release];
[super dealloc];
}
- (void)startNextConnection
{
NSURLRequest *request = [requestQueue objectAtIndex:0];
currentConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)queueRequest:(NSURLRequest *)request delegate:(id)delegate
{
[requestQueue addObject:request];
[delegateQueue addObject:delegate];
if ([requestQueue count] == 1)
[self startNextConnection];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connection: connection didReceiveResponse: response];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connection: connection didReceiveData: data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connection: connection didFailWithError:error];
[currentConnection release];
currentConnection = nil;
[requestQueue removeObjectAtIndex:0];
[delegateQueue removeObjectAtIndex:0];
if ([requestQueue count] >= 1)
[self startNextConnection];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connectionDidFinishLoading: connection];
[currentConnection release];
currentConnection = nil;
[requestQueue removeObjectAtIndex:0];
[delegateQueue removeObjectAtIndex:0];
if ([requestQueue count] >= 1)
[self startNextConnection];
}
#end
To use the connection queue, create an NSURLRequest instance and then call:
[[ConnectionQueue sharedInstance] queueRequest:request delegate:self];
There's no need to create the singleton instance of ConnectionQueue explicitly. It will be created automatically. However, to properly clean up, you should call [ConnectionQueue releaseShared] when the application quits, e.g. from applicationWillTerminate: of your application delegate.