4

EDIT2 - Rewrote the question

I want to do some web service communication in the background. I am using Sudzc as the handler of HTTPRequests and it works like this:

SudzcWS *service = [[SudzcWS alloc] init];
[service sendOrders:self withXML:@"my xml here" action:@selector(handleOrderSending:)];
[service release];

It sends some XML to the webservice, and the response (in this one, a Boolean) is handled in the selector specified:

- (void)handleOrderSending:(id)value
{ 
//some controls  
    if ([value boolValue] == YES)
    {
        //my stuff
    }
}

When I tried to use Grand Central Dispatch on my sendOrders:withXML:action: method, I noticed that the selector is not called. And I believe the reason for that is that NSURLConnection delegate messages are sent to the thread of which the connection is created But the thread does not live that long, it ends when the method finishes, killing any messages to the delegate.

Regards

EDIT1 [request send] method:

- (void) send {
//dispatch_async(backgroundQueue, ^(void){
    // If we don't have a handler, create a default one
    if(handler == nil) {
        handler = [[SoapHandler alloc] init];
    }

    // Make sure the network is available
    if([SoapReachability connectedToNetwork] == NO) {
        NSError* error = [NSError errorWithDomain:@"SudzC" code:400 userInfo:[NSDictionary dictionaryWithObject:@"The network is not available" forKey:NSLocalizedDescriptionKey]];
        [self handleError: error];
    }

    // Make sure we can reach the host
    if([SoapReachability hostAvailable:url.host] == NO) {
        NSError* error = [NSError errorWithDomain:@"SudzC" code:410 userInfo:[NSDictionary dictionaryWithObject:@"The host is not available" forKey:NSLocalizedDescriptionKey]];
        [self handleError: error];
    }

    // Output the URL if logging is enabled
    if(logging) {
        NSLog(@"Loading: %@", url.absoluteString);
    }

    // Create the request
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: url];
    if(soapAction != nil) {
        [request addValue: soapAction forHTTPHeaderField: @"SOAPAction"];
    }
    if(postData != nil) {
        [request setHTTPMethod: @"POST"];
        [request addValue: @"text/xml; charset=utf-8" forHTTPHeaderField: @"Content-Type"];
        [request setHTTPBody: [postData dataUsingEncoding: NSUTF8StringEncoding]];

        if(self.logging) {
            NSLog(@"%@", postData);
        }
    }


    //dispatch_async(dispatch_get_main_queue(), ^(void){
        // Create the connection
        conn = [[NSURLConnection alloc] initWithRequest: request delegate: self];
        if(conn) {
                                        NSLog(@" POST DATA %@", receivedData);
            receivedData = [[NSMutableData data] retain];
                        NSLog(@" POST DATA %@", receivedData);
        } else {
            // We will want to call the onerror method selector here...
            if(self.handler != nil) {
                NSError* error = [NSError errorWithDomain:@"SoapRequest" code:404 userInfo: [NSDictionary dictionaryWithObjectsAndKeys: @"Could not create connection", NSLocalizedDescriptionKey,nil]];
                [self handleError: error];
            }
        }
    //});


    //finished = NO;

    //    while(!finished) {
    //        
    //        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    //        
    //    }

//});
}

The parts that are commented out are the various things I tried. The last part worked but I'M not sure if that's a good way. In the NURLConnection delegate method of the class, here is what happens:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError* error;
if(self.logging == YES) {
    NSString* response = [[NSString alloc] initWithData: self.receivedData     encoding: NSUTF8StringEncoding];
    NSLog(@"%@", response);
    [response release];
}

CXMLDocument* doc = [[CXMLDocument alloc] initWithData: self.receivedData options: 0 error: &error];
if(doc == nil) {
    [self handleError:error];
    return;
}

id output = nil;
SoapFault* fault = [SoapFault faultWithXMLDocument: doc];

if([fault hasFault]) {
    if(self.action == nil) {
        [self handleFault: fault];
    } else {
        if(self.handler != nil && [self.handler respondsToSelector: self.action]) {

                [self.handler performSelector: self.action withObject: fault];


        } else {
            NSLog(@"SOAP Fault: %@", fault);
        }
    }
} else {
    CXMLNode* element = [[Soap getNode: [doc rootElement] withName: @"Body"] childAtIndex:0];
    if(deserializeTo == nil) {
        output = [Soap deserialize:element];
    } else {
        if([deserializeTo respondsToSelector: @selector(initWithNode:)]) {
            element = [element childAtIndex:0];
            output = [deserializeTo initWithNode: element];
        } else {
            NSString* value = [[[element childAtIndex:0] childAtIndex:0] stringValue];
            output = [Soap convert: value toType: deserializeTo];
        }
    }

    if(self.action == nil) { self.action = @selector(onload:); }
    if(self.handler != nil && [self.handler respondsToSelector: self.action]) {


            [self.handler performSelector: self.action withObject: output];


    } else if(self.defaultHandler != nil && [self.defaultHandler respondsToSelector:@selector(onload:)]) {
        [self.defaultHandler onload:output];
    }
}

[self.handler release];
[doc release];
[conn release];
conn = nil;
[self.receivedData release];
}

The delegate is unable to send messages because the thread it is dies when -(void)send finishes.

Eren Beşel
  • 1,047
  • 8
  • 16

2 Answers2

2

The method definition for sendOrders suggests that it is already designed to execute requests in an asynchronous fashion. You should have a look into the implementation of sendOrders: withXML: action: to find out if this is the case.

Without seeing your implementation using GCD or the code from SudzcWS it's hard to say what's going wrong. Despite the preceding caveats, the following might be of use.

It looks like you may be releasing SudzcWS *service before it is completed.

The following:

SudzcWS *service = [[SudzcWS alloc] init];
dispatch_async(aQueue, ^{
    [sevice sendOrders:self withXML:xml action:@selector(handleOrderSending:)];
}
[service release];

could fail unless SudzcWS retains itself. You dispatch your block asynchronously, it gets put in a queue, and execution of the method continues. service is released and gets deallocated before the block executes or while service is waiting for a response from the webserver.

Unless otherwise specified, calling a selector will execute that selector on the same thread it is called on. Doing something like:

SudzcWS *service = [[SudzcWS alloc] init];
dispatch_async(aQueue, ^{
    [sevice sendOrders:self withXML:xml action:@selector(handleOrderSending:)];
}


- (void)handleOrderSending:(id)value
{ 
    //some controls  
    //your stuff
    [service release];
}

should ensure that both the sendOrders: method and the handleOrderSending: are executed on the queue aQueue and that service is not released until it has executed the selector.

This will require you to keep a pointer to service so that handleOrderSending: can release it. You might also want to consider simply hanging onto a single SudzcWS instance instead of creating and releasing one each time you want to use it, this should make your memory management much easier and will help keep your object graph tight.

mjmdavis
  • 2,583
  • 3
  • 19
  • 16
  • Thanks a lot. Firstly, releasing `service` didn't make a difference. Secondly, unfortunately the 2 methods do not run in the same queue either way. Lastly, the way `sendOrders: withXML: action:` works is it checks if the `action` object is not nil and runs `performSelector:`. So now I'm trying to use GCD inside that class (the class that sends the HTTPRequest) instead of here. – Eren Beşel Apr 24 '12 at 07:29
  • If it is running `[object performSelector:selector]` then, in this case it is equivalent to `[object handleOrderSending:]`. This means that the method WILL be performed on the same queue the message is sent on. Can you post the code from SudzcWS? It sounds as though it is already doing some of your work for you which might explain the confusion. How are you determining which queue things are running on? – mjmdavis Apr 24 '12 at 08:55
  • See my edited question. GCD worked fine when I used it inside the handler class, but not on `[service sendOrders: withXML: action:]`. I'm just confused as to why. – Eren Beşel Apr 24 '12 at 10:38
  • I recently found out that the reason the selectors are not getting called when I use GCD with `sendOrders: withXML: action:` is that the delegate method of `NSURLConnection` is not getting called from the background thread. If I can solve this, then I believe I'll be done. Any suggestions? I looked up some other questions but to no avail. – Eren Beşel Apr 24 '12 at 12:41
  • The delegate methods should be 'called in the same thread' that calls the `initWithRequest:delegate:`. You should check which queue you are sending the method from. Note that dispatching something on a specific queue does not mean it will execute on a particular thread! View the debug navigator 'By Queue' to see which queue something is in. You may need to do this on the device. – mjmdavis Apr 24 '12 at 13:26
  • Alright I used this [link](http://www.wim.me/nsurlconnection-in-its-own-thread/) and it seems fine. But is this a good way? – Eren Beşel Apr 24 '12 at 15:16
  • Well, the documentation on NSURLRequest is not entirely clear regarding dispatch queues. NSURLRequest manages the fetching of data in the background anyway so do you really need to put it anywhere special. – mjmdavis Apr 24 '12 at 15:30
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10439/discussion-between-pingbat-and-eren-besel) – mjmdavis Apr 24 '12 at 15:54
2

I've had help from both these links SO NURLConnection question and the original one.

It does not seem risky for my code and I will use it at my own risk. Thanks.

Any recommendations are still welcome of course.

Additional thanks to Pingbat for taking the time to try and help.

Community
  • 1
  • 1
Eren Beşel
  • 1,047
  • 8
  • 16