views:

359

answers:

2

Hi,

I need to use an NSTimer to cancel my NSURLRequest before 75 seconds (the time I measured regardless of the timeout I set). I'm using the XMLRPC classes by Eric Czarny. The XMLRPCConnection is basically an image of the NSURLConnection class.

Here's the interface and the implementation file:

#import <Foundation/Foundation.h>

@class XMLRPCRequest, XMLRPCResponse;

/* XML-RPC Connecion Notifications */
extern NSString *XMLRPCRequestFailedNotification;
extern NSString *XMLRPCSentRequestNotification;
extern NSString *XMLRPCReceivedResponseNotification;

@interface XMLRPCConnection : NSObject {
NSURLConnection *_connection;
NSString *_method;
NSMutableData *_data;
id _delegate;
UIViewController* _requester;
}

@property(nonatomic, retain) NSMutableData* _data;

- (id)initWithXMLRPCRequest: (XMLRPCRequest *)request delegate: (id)delegate;
- (id)initWithXMLRPCRequest: (XMLRPCRequest *)request delegate: (id)delegate             requester:(UIViewController*)requester;

- (void) timedOut;

#pragma mark -

+ (XMLRPCResponse *)sendSynchronousXMLRPCRequest: (XMLRPCRequest *)request;

#pragma mark -

- (void)cancel;

@end

 #pragma mark -

@interface NSObject (XMLRPCConnectionDelegate)

- (void)connection: (XMLRPCConnection *)connection didReceiveResponse: (XMLRPCResponse               *)response
forMethod: (NSString *)method;

  - (void)connection: (XMLRPCConnection *)connection didFailWithError: (NSError *)error
forMethod: (NSString *)method;

 @end

Implementation file:

#import "XMLRPCConnection.h"
#import "XMLRPCRequest.h"
#import "XMLRPCResponse.h"

NSString *XMLRPCRequestFailedNotification = @"XML-RPC Failed Receiving Response";
NSString *XMLRPCSentRequestNotification = @"XML-RPC Sent Request";
NSString *XMLRPCReceivedResponseNotification = @"XML-RPC Successfully Received Response";

@interface XMLRPCConnection (XMLRPCConnectionPrivate)

- (void)connection: (NSURLConnection *)connection didReceiveData: (NSData *)data;
- (void)connection: (NSURLConnection *)connection didFailWithError: (NSError *)error;
- (void)connectionDidFinishLoading: (NSURLConnection *)connection;

- (void) timedOut;

@end

#pragma mark -

@implementation XMLRPCConnection

@synthesize _data;


- (id)initWithXMLRPCRequest: (XMLRPCRequest *)request delegate: (id)delegate requester:(UIViewController*)requester {

    if (self = [super init])
    {
        _connection = [[NSURLConnection alloc] initWithRequest: [request request] delegate: self];
        _delegate = delegate;       
        _requester = requester;


        // set the timer for timed out requests here
        NSTimer* connectionTimer = [NSTimer timerWithTimeInterval:10.0f target:self selector:@selector(timedOut) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:connectionTimer forMode:NSDefaultRunLoopMode];

        if (_connection != nil)
        {
            _method = [[NSString alloc] initWithString: [request method]];
            _data = [[NSMutableData alloc] init];

            [[NSNotificationCenter defaultCenter] postNotificationName:
             XMLRPCSentRequestNotification object: nil];
        }
        else
        {
            if ([_delegate respondsToSelector: @selector(connection:didFailWithError:forMethod:)])
            {
                [_delegate connection: self didFailWithError: nil forMethod: [request method]];
            }

            return nil;
        }
    }

    return self;

}

- (void) timedOut {

    NSLog(@"connection timed out now!");
}


#pragma mark -

+ (XMLRPCResponse *)sendSynchronousXMLRPCRequest: (XMLRPCRequest *)request
{
    NSURLResponse *urlres;
    //NSHTTPURLResponse *urlres;

    NSError *err = NULL;

    // set the timer for timed out requests here
    NSTimer* connectionTimer = [NSTimer timerWithTimeInterval:10.0f target:self selector:@selector(timedOut) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:connectionTimer forMode:NSDefaultRunLoopMode];

    NSData *data = [NSURLConnection sendSynchronousRequest: [request request]
                    returningResponse: &urlres error: &err];



        if ([urlres isKindOfClass:[NSHTTPURLResponse class]]) {

            NSLog(@"Received status code: %d %@", [(NSHTTPURLResponse *) urlres statusCode], 
              [NSHTTPURLResponse localizedStringForStatusCode:[(NSHTTPURLResponse *) urlres statusCode]]) ;

            NSDictionary* headerFields = [(NSHTTPURLResponse*)urlres allHeaderFields];



            NSArray* cookie = [NSHTTPCookie cookiesWithResponseHeaderFields:headerFields forURL:[request host]];

            if ([cookie count] != 0) {
                NSString* cookieName = [[cookie objectAtIndex:0] name];
                NSString* cookieValue = [[cookie objectAtIndex:0] value];
                NSLog(@"cookie name=value: %@=%@", cookieName, cookieValue );
                [[User getInstance] setCookieID:[NSString stringWithFormat:@"%@=%@", cookieName, cookieValue] ];

            } else {
                NSLog(@"cookie array empty!");
            }

    }

    // if an error occured while processing the request, this variable will be set
    if( err != NULL )
    {
        //TODO: we may need to create a XMLRPCResponse with the error. and return
        return (id) err;
    }

    if (data != nil)
    {
        NSString  *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"response is: %@",str);
        if ( ! str ) {
            str = [[NSString alloc] initWithData:data encoding:[NSString defaultCStringEncoding]];
            data = [str dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
        }

            //Check for HTML code 400 or greater in response statusCode (from header) and throw error if so
            if ([urlres isKindOfClass:[NSHTTPURLResponse class]]) {

                // HTTP codes equal or higher than 400 signifies an error
                if ([(NSHTTPURLResponse *) urlres statusCode] >= 400) {

//                  NSLog(@"Received status code: %d %@", [(NSHTTPURLResponse *) urlres statusCode], 
//                        [NSHTTPURLResponse localizedStringForStatusCode:[(NSHTTPURLResponse *) urlres statusCode]]) ;

                    NSString *errorIntString = [NSString stringWithFormat:@"%d", [(NSHTTPURLResponse *) urlres statusCode]];
                    NSString *stringForStatusCode = [NSHTTPURLResponse localizedStringForStatusCode:[(NSHTTPURLResponse *) urlres statusCode]];
                    NSString *errorString = [[errorIntString stringByAppendingString:@" "] stringByAppendingString:stringForStatusCode];

                    NSInteger code = -1; //This is not significant, just a number with no meaning
                    NSDictionary *usrInfo = [NSDictionary dictionaryWithObject:errorString forKey:NSLocalizedDescriptionKey];
                    err = [NSError errorWithDomain:@"org.wordpress.iphone" code:code userInfo:usrInfo];
                    return (id) err;
            }
         }  

        //[str release];
        return [[[XMLRPCResponse alloc] initWithData: data] autorelease];
    }

    return nil;
}

#pragma mark -

- (void)cancel
{
    [_connection cancel];
    [_connection autorelease];
}

#pragma mark -

- (void)dealloc
{
    [_method autorelease];
    [_data autorelease];

    [super dealloc];
}

@end

#pragma mark -

@implementation XMLRPCConnection (XMLRPCConnectionPrivate)

....
@end

The timer set in the initWithXMLRPCRequest method works fine, but if it's set in the sendSycnhronousXMLRPCRequest method, I get the following error:

2010-01-29 11:36:40.442 ActiveE[1071:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[XMLRPCConnection timedOut]: unrecognized selector sent to class 0x32610'
2010-01-29 11:36:40.443 ActiveE[1071:207] Stack: (
    31044699,
    2497855305,
    31426811,
    30996086,
    30848706,
    609709,
    30829248,
    30825544,
    39135117,
    39135314,
    3100675
)

I don't understand, I did declare the timeOut method in the implementation file?

A: 

The method that the timer calls has to be of the form:

- (void)timerFireMethod:(NSTimer*)theTimer;

The name is arbitrary but you must return void and must accept a timer. I suspect that is the source of your error. In any case, you should change it to the standard form.

TechZen
On the iPhone NSTimers work fine without the NSTimer argument. I use them all the time like that. The error is somewhere else. The error message points to the XML-RPC library. It mentions `timedOut`.
St3fan
Thanks! It seems to be the fact that the synchronized method is a class method: https://devforums.apple.com/thread/37609?tstart=0
Leonard
A: 

I think your timedOut method should be a public one. Not hidden in a category with private methods.

St3fan
That is also true - I just repaired it. The interfaces are mixed with the implementation and I got completely lost in that.
Leonard