Question

I am using GCDAsyncSocket (CocoaAsyncSocket) for the socket communication in my app. Due to the asynchronous nature of GCDAsyncSocket, my network request (submitMessage below) is decoupled from the callback block that runs when data is received (socket:didReadData).

- (void)submitMessage:(NSDictionary *)messageObject onCompletion:(completionBlock)block {
    ...
    [_socket writeData:requestData withTimeout:self.timeout tag:0];
    [_socket readDataToLength:4 withTimeout:self.timeout tag:TAG_HEADER];
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    ...
    NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
    if (self.completionBlock != nil)
        self.completionBlock(responseObject);
    }
}

This approach works fine for one-off exchanges. But there are some cases when I need to post a request, then using the received data, post another request. I can't get this to work properly. Basically, I need something like this:

[self submitMessage:request1 onCompletion:^(NSDictionary *response1) {
    (...callback 1...)
    }];
    [self submitMessage:request2 onCompletion:^(NSDictionary *response2) {
        (...callback 2...)
    }];
}];

or

[self submitMessage:request1 onCompletion:^(NSDictionary *response1) {
    (...callback 1...)
}];
[self submitMessage:request2 onCompletion:^(NSDictionary *response2) {
    (...callback 2...)
}];

where the order is strictly request1 - callback1 - request2 - callback2.

So the question is, how can I block the second request to run after the callback of the first request? Would GCD (dispatch_sync?) be the way to go?

Edit

I ended up using a solution similar to what @tigloo suggested (hence accepting his answer), but using NSCondition instead of GCD (if anyone's interested in details, I followed this great discussion). I am already running multiple threads (UI in main, high-level socket comms in another thread, and the socket operations in a third thread). Setting a class property and using NSCondition to lock the GCDAsyncSocket delegate until the response arrive seems the cleanest approach.

Was it helpful?

Solution 2

The easiest approach is to append your requests to a serial dispatch queue and then wait for them to be completed by using dispatch_sync(). A discussion on StackOverflow can be found here.

The actual way of implementing it is up to your preferences. A possible idea is the following:

  • Create a new class "SyncRequest"
  • This class ideally has a private property of type bool "requestFinished", initialized to NO in the class' init method
  • In a method such as "sendSyncRequest" you call submitMessage:completionBlock:
  • The completion block will set the "requestFinished" property to YES
  • The last line in "sendSyncRequest" will be dispatch_sync(syncRequestQueue, ^(void){while(!requestFinished);});

This way you can construct multiple instances of SyncRequest, each handling a synchronized request. Rough sketch implementation:

@interface SyncRequest
@property bool requestFinished;
@end

@implementation SyncRequest

dispatch_queue_t syncRequestQueue;    

-(id)init
{
   self = [super init];
   if ( !self )
      return nil;

   self.requestFinished = NO;
   syncRequestQueue = dispatch_queue_create("com.yourid.syncrequest", DISPATCH_QUEUE_SERIAL);

   return self;
}

-(void) sendSyncRequest:(NSDictionary*)messageObject
{
   // submit message here and set requestFinished = YES in completion block

   // wait for completion here
   dispatch_sync(syncRequestQueue, ^(void){while(!self.requestFinished);});
}

@end

NOTE: I wrote the code without having the compiler at hand, you may have to create an indirect reference to "self" in the dispatch_sync call in order to avoid a cyclic reference.

OTHER TIPS

I think you were almost there. What about

[self submitMessage:request1 onCompletion:^(NSDictionary *response1) {
    // here, do something with response1 and create request2...
    // then you can make request2 directly at the end of the callback:
    [self submitMessage:request2 onCompletion:^(NSDictionary *response2) {
        // here, do something with response2...
    }];
}];

No need for the GCD directives, no need to block execution (which is a bad practice anyway). Does this solve your problem?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top