Reputation: 547
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.
Upvotes: 2
Views: 4056
Reputation: 2576
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?
Upvotes: 2
Reputation: 654
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:
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.
Upvotes: 1