I'm trying to send some images file (almost 100MB) to my iDevice clients using GCDAsyncSocket.
I want to Synchronously send packets to the clients. I mean after sending 100MB of data to first client iterating to the next client.but because of Asynchronous nature of GCDAsyncSocket I don't know how can I serialize these packet sending.
I can't use semaphore because before sending images I negotiate with each client to know what images I should send then try to send those images. and I can't find a neat way to wait and signal the semaphore.
- (void)sendImagesToTheClients:clients
{
...
//negotiating with client to know which images should sent
...
for(Client* client in clients)
{
packet = [packet addImages: images];
[self sendPacket:packet toClient:client];
}
}
- (void)sendPacket:packet toClient:client
{
// Initialize Buffer
NSMutableData *buffer = [[NSMutableData alloc] init];
NSData *bufferData = [NSKeyedArchiver archivedDataWithRootObject:packet];
uint64_t headerLength = [bufferData length];
[buffer appendBytes:&headerLength length:sizeof(uint64_t)];
[buffer appendBytes:[bufferData bytes] length:[bufferData length]];
// Write Buffer
[client.socket writeData:buffer withTimeout:-1.0 tag:0];
}
this is how AsyncSocket writing data works:
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
if ([data length] == 0) return;
GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
{
[writeQueue addObject:packet];
[self maybeDequeueWrite];
}
}});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
}
so how can I synchronize this task?
UPDATE
for socket connection I use GCDAsyncSocket which heavily uses delegation for event notification.(GCDAsyncSocket.h and GCDAsyncSocket.m) (no method with completionHandler).
I have written a class named TCPClient which handles socket connection and packet sending and set it as the delegate of initialized socket.
after writing a packet, the delegate method - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
gets called. which only informs me some data has been written. here I can't decide based of written data to call dispatch_group_leave
. so relying delegate method is useless.
I have modified [client.socket writeData:buffer withTimeout:-1.0 tag:0] in GCDAsyncSocket.h and .m files to accept a completionBlock: [client.socket writeData:buffer withTimeout:-1.0 tag:0 completionBlock:completionBlock]
using this approach helps me to solve synchronizing async tasks.
// perform your async task
dispatch_async(self.socketQueue, ^{
[self sendPacket:packet toClient:client withCompletion:^(BOOL finished, NSError *error)
{
if (finished) {
NSLog(@"images file sending finished");
//leave the group when you're done
dispatch_group_leave(group);
}
else if (!finished && error)
{
NSLog(@"images file sending FAILED");
}
}];
but the problem is after updating GCDAsyncsocket, my code may break. here I'm looking for neat way to add completion handler to GCDAsyncsocket without modifying it directly. like creating a wrapper around it or using features of objective-c runtime. do you have any idea?
You can accomplish this with dispatch groups. For a async task with a completion block:
//create & enter the group
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
// perform your async task
dispatch_async(socketQueue, ^{
//leave the group when you're done
dispatch_group_leave(group);
});
// wait for the group to complete
// (you can use DISPATCH_TIME_FOREVER to wait forever)
long status = dispatch_group_wait(group,
dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC * COMMAND_TIMEOUT));
// check to see if it timed out, or completed
if (status != 0) {
// timed out
}
Alternatively for a task with a delegate:
@property (nonatomic) dispatch_group_t group;
-(BOOL)doWorkSynchronously {
self.group = dispatch_group_create();
dispatch_group_enter(self.group);
[object doAsyncWorkWithDelegate:self];
long status = dispatch_group_wait(self.group,
dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC * COMMAND_TIMEOUT));
// A
if (status != 0) {
// timed out
return NO
}
return YES;
}
-(void)asyncWorkCompleted {}
// after this call, control should jump back to point A in the doWorkSynchronously method
dispatch_group_leave(self.group);
}