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);
}
Related
I am looking at the Ray Wenderlich tutorial on using dispatch queues to get notified when a group of tasks complete. http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2
The first code shown under "Code that works" is straight from the tutorial. The Alert view(final completion block) get executed after all 3 downloads complete.
I tried to play around with it and moved the dispatch async down in the "Code that does not work" to see what will happen if dispatch_group_create() and dispatch_group_enter() happen on different queues. In this case, the dispatch_group_enter() does not seem to register because the dispatch_group_wait() immediately completes and alert view(final completion block) is executed even before all the downloads have completed.
Can someone explain whats happening in the second case? (This is just for my understanding of how dispatch group works and I realize thats its better to put the entire function in the global concurrent queue to avoid blocking the main thread).
Code that works
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++)
{
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup);
__block Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
NSLog(#"Finished completion block for photo alloc for URL %# and photo is %#",url,photo) ;
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
NSLog(#"Finished adding photo to shared manager for URL %# and photo is %#",url,photo) ;
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
NSLog(#"Executing completion block after download group complete") ;
completionBlock(error);
}
}) ;
}) ;
}
EDITED Code that does not work with extra NSLog statements
Code that does not work
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++)
{
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{
dispatch_group_enter(downloadGroup);
NSLog(#"Enetered group for URL %#",url) ;
__block Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
NSLog(#"Finished completion block for photo alloc for URL %# and photo is %#",url,photo) ;
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
NSLog(#"Finished adding photo to shared manager for URL %# and photo is %#",url,photo) ;
}) ;
}
NSLog(#"Executing wait statement") ;
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
NSLog(#"Executing completion block after download group complete") ;
completionBlock(error);
}
}) ;
}
The "dispatch_group_enter() does not seem to register" because it hasn't actually been called yet by the time that dispatch_group_wait() is called. Or, rather, it's not guaranteed to have been called. There's a race condition.
This isn't specifically about different queues. It's about concurrency and asynchronicity.
dispatch_async() just means "add a task to a list" with an implicit understanding that something, somewhere, somewhen will take tasks off of that list and execute them. It returns to its caller immediately after the task has been put on the list. It does not wait for the task to start running, let alone complete running.
So, your for loop runs very quickly and by the time it exits, it may be that none of the tasks that it has queued have started. Or, if any have started, it may be that they haven't finished entering the group.
Your code may complete its call to dispatch_group_wait() before anything has entered the group.
Usually, you want to be sure that all relevant calls to dispatch_group_enter() have completed before the call to dispatch_group_wait() is made. The easiest way to do that is to have them all happen synchronously in one execution context. That is, don't put calls to dispatch_group_enter() inside blocks that are dispatched asynchronously.
I have a class with a single method, that uses a URLConnection to send a serialized NSDictionary to a script at a certain URL, and then calls a completion block. Here is the code for that method:
- (void)sendDictionary:(NSDictionary *)dictionary toScript:(NSString *)scriptName completion:(void (^) (id response))completionBlock
{
...Serialize data and add it to an NSURLRequest request...
H2URLConnection *connection = [[H2URLConnection alloc]initWithRequest:request];
//Define a semaphore to block execution of later statements until the signal is received.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[connection setCompletionBlock:[^(id obj, NSError *err)
{
if (!err) {
//Catch the server response
NSString *receivedString = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding];
NSLog( #"ChecklistAppNetworkManager received string: %#", receivedString);
//Convert the JSON response into an NSDictionary
NSError *otherError;
id deserializedJSON = [NSJSONSerialization JSONObjectWithData:obj options:kNilOptions error:&otherError];
if (otherError) {
NSLog(#"ChecklistAppNetworkManager JSON Error: %#", otherError.description);
}
[completionBlock invoke];
NSLog(#"ChecklistAppNetworkManager JSON Response: %#", deserializedJSON);
//Dispatch the semaphore signal so that the main thread continues.
dispatch_semaphore_signal(sem);
} else {
NSLog(#"ChecklistAppNetworkManager encountered an error connecting to the server: %#", [err description]);
}
}copy]];
//Finalize and initate the connection.
[connection start];
//Since block is dispatched to main queue, stall with a loop until the semaphore signal arrives.
while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
}
I'm trying to call this method on an instance of this class from within another class, where the completion block is defined. Here's the code where I get the EXC_BAD_ACCESS:
- (void)doSomeServerTask
{
H2ChecklistAppNetworkManager *currentNetworkManager = ((H2AppDelegate *)[[UIApplication sharedApplication]delegate]).networkManager; //Instantiate class where that method is defined
NSMutableDictionary *dictonary = [NSMutableDictionary dictionary];
...populate dictionary...
[currentNetworkManager sendDictionary:dictionary toScript:#"script.php" completion:[^(id response)
{ //THIS iS THE LINE WHERE THE BAD ACCESS OCCURS
NSLog(#"LoginViewController received response: %#", response);
} copy]];
}
Any help would be appreciated!
The completionBlock on that method takes one argument, but you call the block with the invoke method. More likely than not, the crash is because the runtime is trying to retain whatever garbage is in memory that should be that argument.
However, you really need to refactor this code entirely. Blocking the main event loop is bad. Running a sub-runloop is even worse on the MEL; it changes the way dispatch queue handling semantics work and can lead to pathologically bad performance or behavior.
You should move to a truly asynchronous model. If the app can't proceed until these queries are done, then put up a modal indicator that blocks progress.
To do that, you structure the code loosely as:
• put user interface into a "loading..." or some other modal state
• execute an asynchronous request for data with a completion handler
• in the completion handler, dispatch the "update UI" request to the main queue
• upon "update UI", tear down your modal "loading...." UI and update the display for the user
There is no need to block the main event loop to do any of this.
I have a senario that requires me to make multiple call to a web api. The following is an example.
getDataAsync:(NSDictionary *)dictionary withCompletion: (void (^)(NSDictionary*))completion {
__block int counter = n; // the number of async blocks
__block NSMutableDictionary *output = [[NSMutableDictionary alloc] init];
void (^returnBlock)(void) = ^{
counter--;
if(counter != 0) return;
completion(#{#"return": output});
return;
};
void (^getResourceA)(void) = ^{
[service getResourceA : dictionary[#"idA"] completion:
^(ServiceResult results, MyResourceA *a, NSString *errMsg) {
[output setValue:a.value forKey:a.name];
returnBlock();
}];
};
// followed by n-1 other blocks like getResourceA
//...
}
I want to use the built in dispatch_queue rather than my own custom solution here. How can I do that given the inner completion block used by the asynchronous service call?
Also any other advice on how to go about this would be appreciated.
Dispatch groups have been invented for this purpose:
dispatch_group_t requestGroup = dispatch_group_create();
dispatch_group_async(requestGroup, queue, ^{
// ...
});
dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
completionBlock();
Or instead of waiting:
dispatch_group_notify(requestGroup, dispatch_get_main_queue(), ^{
completionBlock();
});
Also, instead of dispatching blocks to the group, you can also enter and leave a group manually, which works well with asynchronous service APIs:
dispatch_group_enter(requestGroup);
[service getResourceA : dictionary[#"idA"] completion: ^(ServiceResult results, MyResourceA *a, NSString *errMsg) {
[output setValue:a.value forKey:a.name];
dispatch_group_leave(requestGroup);
}];
Use dispatch_group_t. See Waiting on Groups of Queued Tasks.
The topic doesn't mention it, but use dispatch_group_notify to register a block instead of waiting inline.
I have a small project that reads an HTTP stream from a remote server, demuxes it, extracts audio stream, decodes it into 16-bit PCM, and feeds into a corresponding AudioQueue. The decoder/demuxer/fetcher runs in a separate thread and it uses my home-grown blocking queue (see code below) to deliver the decoded frames to the AudioQueue callback. The queue uses NSMutableArray to store objects.
Once this thing is in-flight, it leaks objects inserted into the queue. Memory profiler says that the RefCt is 2 by the time I expect it to be 0 and to be released by ARC.
Here are the queue/dequeue methods:
- (id) dequeue {
dispatch_semaphore_wait(objectsReady, DISPATCH_TIME_FOREVER);
[lock lock];
id anObject = [queue objectAtIndex:0];
[queue removeObjectAtIndex:0];
[lock unlock];
dispatch_semaphore_signal(freeSlots);
return anObject;
}
- (void) enqueue:(id)element {
dispatch_semaphore_wait(freeSlots, DISPATCH_TIME_FOREVER);
[lock lock];
[queue addObject:element];
[lock unlock];
dispatch_semaphore_signal(objectsReady);
}
Producer thread does this:
[pAudioFrameQueue enqueue:[self convertAVFrameAudioToPcm:audioFrame]];
And the "convertAVFrameAudioToPcm" methods looks like this:
- (NSData*) convertAVFrameAudioToPcm:(AVFrame*)frame {
NSData* ret = nil;
int16_t* outputBuffer = malloc(outputByteLen);
// decode into outputBuffer and other stuff
ret = [NSData dataWithBytes:outputBuffer length:outputByteLen];
free(outputBuffer);
return ret;
}
Consumer does this:
- (void) fillAvailableAppleAudioBuffer:(AudioQueueBufferRef)bufferToFill {
#autoreleasepool {
NSData* nextAudioBuffer = [pAudioFrameQueue dequeue];
if (nextAudioBuffer != nil) {
[nextAudioBuffer getBytes:bufferToFill->mAudioData]; // I know this is not safe
bufferToFill->mAudioDataByteSize = nextAudioBuffer.length;
} else {
NSLog(#"ERR: End of stream...");
}
}
}
To me it looks like RefCt should become 0 when fillAvailableAppleAudioBuffer exits, but apparently ARC disagrees and does not release the object.
Am I having a bug in my simple queue code?
Or do I instantiate NSData in a wrong way?
Or am I missing some special rule of how ARC works between threads? By the way, the producer threads starts like this:
- (BOOL) startFrameFetcher {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL),
^(void) {
[self frameFetcherThread];
});
return YES;
}
Any hints will be much appreciated!
PS: and last but not the least, I do have another instance of the same blocking queue that stores video frames that I dequeue and show via NSTimer. Video frames do not leak! I am guessing this may have something to do with threading. Otherwise, I would have expected to see the leak in both queues.
I'm using an AFNetworking client object which makes an asynchronous request for an XML document and parses it.
Also using NSNotificationCenter to post a notification when the document has finished parsing.
Is there a way to wait for a notification to be posted without blocking the main thread?
E.g code:
-(void)saveConfiguration:(id)sender {
TLHTTPClient *RESTClient = [TLHTTPClient sharedClient];
// Performs the asynchronous fetching....this works.
[RESTClient fetchActiveUser:[usernameTextField stringValue] withPassword:[passwordTextField stringValue]];
/*
* What do I need here ? while (xxx) ?
*/
NSLog(#"Fetch Complete.");
}
Basically I'm wondering what sort of code I need in the above specified area to ensure that the function waits until the fetch has been completed ?
As it is right now I'll see "Fetch Complete." in the debug console before the fetch has been completed.
I tried adding a BOOL flag to the TLHTTPClient class:
BOOL fetchingFlag;
and then trying:
while([RESTClient fetchingFlag]) { NSLog(#"fetching...); }
When this class receives the notification it sets RESTClient.fetchingFlag = FALSE; which should technically kill the while loop right? Except my while loop runs infinitely ?!
Basically I'm wondering what sort of code I need in the above specified area to ensure that the function waits until the fetch has been completed ?
You need no code. Don't put anything in the method after you start the fetch, and nothing will happen. Your program will "wait" (it will actually be processing other input) until the notification is recieved.
In the notification handler method, put all the stuff that you need to do when the fetch is completed. This is (one of) the point(s) of notifications and other callback schemes -- your object won't do anything further until it gets the notification that it's time to act.
Is there a way to wait for a notification to be posted without blocking the main thread?
That's exactly how it works already.
If you don't need to inform multiple objects upon completion of the task, you could add a completion handler (objc block) to the -fetchActiveUser:withPassword: method (so it would become something like -fetchActiveUser:withPassword:completionHandler: and add the code to be executed in the completion handler.
An example, lets say your -fetchActiveUser:withPassword:completionHandler: method looks like the following:
- (void)fetchActiveUser:(NSString *)user
withPassword:(NSString *)pass
completionHandler:(void (^)(TLUser *user, NSError *error))handler
{
NSURL *URL = [NSURL URLWithString:#"http://www.website.com/page.html"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
NSOperationQueue *queue = [NSOperationQueue currentQueue];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^ (NSURLResponse *response, NSData *data, NSError *error)
{
if (!handler)
{
return
};
if (data)
{
TLUser *user = [TLUser userWithData:data];
if (user)
{
handler(user, nil);
}
else
{
NSError *error = // 'failed to create user' error ...
handler(nil, error);
}
}
else
{
handler(nil, error);
}
}];
}
The completion handler will be called whenever the task is finished. It will either return a TLUser object or an Error if something went wrong (bad connection, data format changed while parsing, etc...).
You'll be able to call the method like this:
- (void)saveConfiguration:(id)sender
{
TLHTTPClient *RESTClient = [TLHTTPClient sharedClient];
// Performs the asynchronous fetching
[RESTClient fetchActiveUser:[usernameTextField stringValue]
withPassword:[passwordTextField stringValue]
completionHandler:^ (TLUser *user, NSError *error)
{
if (user)
{
NSLog(#"%#", user);
}
else
{
NSLog(#"%#", error);
}
}];
}
Of course, in this example I've used the build in asynchronous methods of NSURLConnection in stead of AFNetworking, but you should be able to get the general idea.