dispatch_group_notify does not wait for one dispatch_group_async - ios

Two concurrent background tasks need to patch two separate arrays which need to be merged in a dispatch_group_notify block.The problem is that.the first block is exceeded but the dispatch_group_notify is exceeded without waiting for the execution of the second background task.
The only different between them is that the first one make a local search and the second one makes a remote call to a web service.Any clue why the second one is jumped over ?
Edit: I also tried the approach mentioned in https://stackoverflow.com/a/19580584/859742 using dispatch_barrier_async but still same.
dispatch_group_t taskGroup = dispatch_group_create();
dispatch_queue_t mainQueue = dispatch_get_main_queue();
__block NSArray *localAddresses;
__block NSArray *remoteAddresses;
//Get local array in the background
dispatch_group_async(taskGroup, mainQueue, ^{
//localAddresses is set
});
//get remote array from server
dispatch_group_async(taskGroup, mainQueue, ^{
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
completion:^(NSArray* addresses, MDError *error){
//remoteAddresses is set
});
//Merge two arrays
dispatch_group_notify(taskGroup, mainQueue, ^{
//remoteAddresses and local addresses are merged
});
And the remote search method looks like this
- (void)searchForPlacesContainingText:(NSString *)searchText
location:(CLLocation *)alocation
completion:(MDAddressManagerBlock)completionBlock
{
NSDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:searchText forKey:#"input"];
[[MDHTTPClient sharedHTTPClient] getPath:#"v1/remotePlaces.json"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id dict) {
if ([MDHTTPClient isResponseValid:dict])
{
completionBlock(returnArray, nil);
}
else
{
completionBlock(nil, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
EDLog(#"%#", error);
completionBlock(nil, [MDError errorAFNetworking:error]);
}];
}

This is because your getPath method runs asynchronously. You need it to not leave the group until that completion block runs. So rather than than doing dispatch_group_async, you should manually dispatch_group_enter and dispatch_group_leave.
You can change your code from:
dispatch_group_async(taskGroup, mainQueue, ^{
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
completion:^(NSArray* addresses, MDError *error){
//remoteAddresses is set
}];
});
To:
dispatch_group_enter(taskGroup);
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
completion:^(NSArray* addresses, MDError *error){
//remoteAddresses is set
dispatch_group_leave(taskGroup);
});
That will ensure that you don't leave the group until the completion block is called.
Alternatively, you could change searchForPlacesContainingText to use dispatch_group_t parameter:
- (void)searchForPlacesContainingText:(NSString *)searchText
location:(CLLocation *)alocation
group:(dispatch_group_t)group
completion:(MDAddressManagerBlock)completionBlock
{
dispatch_group_enter(group);
NSDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:searchText forKey:#"input"];
[[MDHTTPClient sharedHTTPClient] getPath:#"v1/remotePlaces.json"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id dict) {
if ([MDHTTPClient isResponseValid:dict])
{
completionBlock(returnArray, nil);
}
else
{
completionBlock(nil, nil);
}
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
EDLog(#"%#", error);
completionBlock(nil, [MDError errorAFNetworking:error]);
dispatch_group_leave(group);
}];
}
and change your invocation so that it doesn't do dispatch_group_async, but rather just passes the taskGroup parameter:
[[MDAddressManager instance] searchForPlacesContainingText:query
location:alocation
group:taskGroup
completion:^(NSArray* addresses, MDError *error) {
//remoteAddresses is set
});

Related

How to freeze my app to wait for respond?

I have a simple app which try to login users.
User insert username and password
I call a method in another class ->
if ([myBankLogger checkUserLogin:self.memberNumber.text :self.accessCode.text])
{
//Check if user credential is correct
NSLog(#"Corrct");
}
else
{
NSLog(#"Not correct");
}
In checkUserLogin I send a http request to server and it takes a minute to get respond:
-(bool) checkUserLogin :(NSString*)username :(NSString*)password
{
__block bool tmp= false;
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:string parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON: %#", responseObject);
if (responseObject[#"secret_token"])
{
NSLog(#"HERE");
tmp = true;
}
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
tmp = false;
}];
return tmp;
}
As it is normal compiler dose not wait until it finish processing. It returns false. I researched and found out I must use this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Perform async operation
// Call your method/function here
// Example:
// NSString *result = [anObject calculateSomething];
dispatch_sync(dispatch_get_main_queue(), ^{
// Update UI
// Example:
// self.myLabel.text = result;
});
});
But I am not sure where I should put my code? Appreciate any suggestion or easier solution.
If you perform a synchronous operation on the main thread, this will block the app UI for the entire duration of that sync operation. If it takes too much, the OS will kill the application execution. With an async approach using the dispatch method, you can solve the UI blocking problem:
[self.myRingLoading startAnimating]; // show a ring loading while the following long operation will take (here you are in the main thread scope)
// this dispatch block will move all the actions in an async thread, avoiding any UI blocking issue
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// Perform here your UI blocking operation
// sample blocking ui operation
NSData* httpDataResult = [NSData dataWithContentsOfURL:#"http://api.yoursite.com/xyz"];
NSString* httpStringResult = [[NSString alloc] initWithData:httpDataResult encoding:NSUTF8StringEncoding];
// this dispatch block will send all action execution to the main queue, commonly the main thread
dispatch_async(dispatch_get_main_queue(), ^(void){
// Perform your UI operation (eg: printing the http response received by the server
[self.myUILabel setText:httpStringResult];
[self.myRingLoading stopAnimating]; // hide the ring loading since the long operation is finished
});
});
There are other native solutions for obtaining the result, for example using the NSOperation and NSOperationQueue. If you use AFNetworking, you can take advantage of several good implementations, for example AFHTTPRequestOperation:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Perform your UI actions for the Success case
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Perform your UI actions for the Failed case
}
];
// run the http operation process
[operation start];
Hope it helps

Returning a BOOL method inside a block?

I have this bool method that returns a yes or no for an inputted string.
I'm successfully able to return a YES or a NO, but I cannot seem to able to make a network connection and return a YES or a NO depending on the server's response.
I tried using __block and I don't feel like that will wait for the web request to finish, is there a way to return YES or NO in the success block without it giving me the error:
Incompatible block pointer types sending 'BOOL(^)(NSURLSessionTask*__strong, NSError __strong' to parameter of the type 'void(^)(NSURLSessionTask...)
-(BOOL)customResponseForString:(NSString *)text {
__block BOOL response_available;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:#"text/plain"]];
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//return NO;
}];
});
return response_available;
}
Your block definition syntax is probably erroneous, because you can definitely return a BOOL along other parameters in a block.
- (void)fetchCurrentUserWithCompletion:(void (^)(BOOL success, User *user))completion;
This method would be called like this:
[self.userProfileController fetchCurrentUserWithCompletion:^(BOOL success, User *user) {
if (success) {
NSLog(#"Current User Name: %#", user.fullName);
}
}];
If you use AFNetworking, check the AFHTTPRequestOperation object that handle completionBlocks:
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
User *user = [self userFromResponseObject:responseObject];
if (completion) completion(YES, user);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) completion(NO, user);
}];
Because you are implicitly initializing response_available to NO and then using an async GCD call, your method as written will always immediately return NO without waiting for the request to finish. Note: switching to dispatch_sync won't help either because AFNetworking will queue the GET request asynchronously either way.
Best Approach
Add a completion block argument to customResponseForString:. Then simply execute your completion block in the success or failure blocks of the AFHTTPRequestOperation.
Workable Approach (use caution!)
It is possible to make customResponseForString: wait for a response to the network request, but you will have significant issues if it is ever called from the main thread.
First you create a dispatch group and tell it you are starting some long-running work:
dispatch_group_t networkGroup = dispatch_group_create();
dispatch_group_enter(networkGroup);
Then you need to make your network request and when it completes tell the group that the work is finished with dispatch_group_leave():
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
dispatch_group_leave(networkGroup);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
response_available = NO;
dispatch_group_leave(networkGroup);
}];
Before your original method returns, tell it to wait for the entire group to finish processing:
dispatch_group_wait(networkGroup, DISPATCH_TIME_FOREVER);
return response_available;
You could adjust this time interval as needed or leave it at DISPATCH_TIME_FOREVER to let the network request time out on its own.

HTTP call runs first time but then waits forever

I'm using SDWebImage to load images asynchronously. I want to combine comments and photos in a dictionary like below:
- (NSArray *) fetchPhotos:(NSArray *) requestedPhotoArray
{
NSMutableArray *photos;
UIImageView *imageview;
for (requestedPhoto in requestedPhotoArray) {
[imageview setImageWithURL:[NSURL URLWithString: requestedPhoto.url]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
NSDictionary *parameters = #{ #"function":GET_COMMENT,
#"photoId":requestedPhoto.photoID,
}
NSArray *comments = [[self.communicator sharedInstance] HTTPRequestWithParams:parameters];
Photo *photo = [Photo photoWithDictionary:
#{#"imageView": imageview,
#"comments" : comments,
}];
[photos addObject:photo];
}
return photos;
}
But fetchPhotos function makes one http call and waits forever and then nothing returns.
fetchPhotos is called like below (simplified version):
NSDictionary *parameters = #{#"function":GET_PHOTO_INFO_ARRAY,
#"userid":3,
#"pageid":99,
}
dispatch_async(dispatch_get_global_queue(0, 0), ^{
requestedPhotosInfoArray = [[self.communicator sharedInstance] HTTPRequestWithParams:parameters];
dispatch_async(dispatch_get_main_queue(), ^{
[self fetchPhotos: requestedPhotosInfoArray];
}
communicator:HTTPRequestWithParams do a HTTP request like below
...
__block id result;
dispatch_queue_t queue = dispatch_queue_create("my_queue", 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async( queue, ^{
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
result = responseObject;
dispatch_semaphore_signal(semaphore);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
result = error;
dispatch_semaphore_signal(semaphore);
}
];
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return result;
Any idea why fetchPhotos only returns first data and waits forever?
UPDATE:
I realised that it waits in
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
then I add a async dispatch queue in fetchPhotos
for (requestedPhoto in requestedPhotoArray) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[imageview setImageWithURL:[NSURL URLWithString: requestedPhoto.url]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
NSDictionary *parameters = #{ #"function":GET_COMMENT,
#"photoId":requestedPhoto.photoID,
}
NSArray *comments = [[self.communicator sharedInstance] HTTPRequestWithParams:parameters];
....
It's not waiting forever now but it doesn't make a http call.
I used block callback instead of semaphores
dispatch_async( queue, ^{
[manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (failure) {
failure(error);
}
}
];
});

AFHTTPSessionManager and NSOperation queues with dependencies (AFNetworking 2)

On previous versions of AFNetworking I could make use of AFHTTPRequestOperation to create multiple requests, create dependencies between them and enqueue them pretty easily. Example (inside of an AFHTTPClient subclass):
NSURLRequest *categoriesRequest = [self requestWithMethod:#"GET" path:#"categories" parameters:nil];
AFHTTPRequestOperation *categoriesOperation = [self HTTPRequestOperationWithRequest:categoriesRequest success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonCategories = responseObject;
for (NSDictionary *jsonCategory in jsonCategories) {
SPOCategory *category = [[SPOCategory alloc] initWithDictionary:jsonCategory];
[self.categories addObject:category];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// …
}];
NSURLRequest *incidencesRequest = [self requestWithMethod:#"GET" path:#"incidences" parameters:nil];
AFHTTPRequestOperation *incidencesOperation = [self HTTPRequestOperationWithRequest:incidencesRequest success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonIncidences = responseObject;
for (NSDictionary *jsonIncidence in jsonIncidences) {
SPOIncidence *incidence = [[SPOIncidence alloc] initWithDictionary:jsonIncidence];
[self.incidences addObject:incidence];
}
completionBlock(self.incidences, self.categories, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// …
}];
[incidencesOperation addDependency:categoriesOperation];
[self enqueueBatchOfHTTPRequestOperations:#[categoriesOperation, incidencesOperation] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
// Processing…
} completionBlock:^(NSArray *operations) {
// Completed
}];
I know I can continue to make use of AFHTTPRequestOperation but, I'd like to know if there is a similar way to achieve the same thing inside a subclass of AFHTTPSessionManager, using NSURLSession as the backing library instead of NSURLConnection.
Thank you!
AFHTTPSessionManager's connection factory methods create connections which will be represented by a NSURLSessionDataTask object.
Unlike AFHTTPRequestOperation these are not NSOperation subclasses, and thus declaring dependencies is not possible.
One could imagine to wrap a factory method like
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
into a helper method/function which returns a NSOperation object. That might (will) become cumbersome and looks quite weird, though.
If you are courageous enough to consider another third party library, you can solve your problem as explained below:
The idea is to represent the eventual result of the asynchronous operation by a "Promise". Think of a Promise as a placeholder of the result, which will eventually be set by the operation. So, basically you wrap a factory method into one which then effectively yields a method having this signature:
-(Promise*) fetchCategories;
or
-(Promise*) fetchCategoriesWithParameters:(NSDictionary*)parameters;
Notice that above methods are asynchronous - yet they have no completion handler. The Promise will instead provide this facility.
Initially, when fetchCategories returns, the promise object does not "contain" the result.
You obtain (at some tme later) the eventual result respectively and error by "registering" a completion handler block respectively an error handler block with a then property like so (pseudo code):
[self.fetchCategoriesWithParameters].then(
<success handler block>,
<failure handler block> );
A more complete code snippet:
Promise* categoriesPromise = [self fetchCategories];
categoriesPromise.then(^id(id result){
self.categories = result;
... // (e.g, dispatch on main thread and reload table view)
return nil;
}, ^id(NSError* error){
NSLog(#"Error: %#", error);
return nil;
});
Note: The parameter result of the success handler block is the eventual result of the operation, aka the responseObject.
Now, in order to "chain" multiple asynchronous operations (including the handlers), you can do this:
self.categoriesPromise = [self fetchCategories];
Promise* finalResult = self.categoriesPromise.then(^id(id result){
NSArray *jsonCategories = result;
for (NSDictionary *jsonCategory in jsonCategories) {
SPOCategory *category = [[SPOCategory alloc] initWithDictionary:jsonCategory];
[self.categories addObject:category];
}
return [self fetchIncidencesWithParams:result);
}, nil)
.then(^id(id result){
NSArray *jsonIncidences = result;
for (NSDictionary *jsonIncidence in jsonIncidences) {
SPOIncidence *incidence =
[[SPOIncidence alloc] initWithDictionary:jsonIncidence];
[self.incidences addObject:incidence];
}
return #[self.incidences, self.categories];
}, nil)
.then(^id(id result){
NSArray* incidences = result[0];
NSArray* categories = result[1];
...
return nil;
}, nil /* error handler block */);
You create and "resolve" (that is, setting the result) a Promise as shown below:
- (Promise*) fetchCategories {
Promise* promise = [[Promise alloc] init];
NSURLRequest *categoriesRequest = [self requestWithMethod:#"GET" path:#"categories" parameters:nil];
AFHTTPRequestOperation *categoriesOperation = [self HTTPRequestOperationWithRequest:categoriesRequest success:^(AFHTTPRequestOperation *operation, id responseObject) {
[promise fulfillWithResult:responseObject];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[promise rejectWithReason:error];
}];
return promise;
}
Disclaimer:
There are a few third party Objective-C libraries which implement a Promise in this or a similar way. I'm the author of RXPromise which implements a promise according the Promises/A+ specification.

Perform upload after user selects Activity from UIActivityViewController - Possibly an issue with blocks

I want to perform an upload task once a user has selected a share activity from UIActivityViewController, but before the share sheet is shown.
Specifically, I need the url of the uploaded image to use in the Activity.
I already have subclassed UIActivityItemProvider and figure I can do my uploading in the itemForActivityType method, however the uploading code is block based and I can't figure out how to make it wait for the block to finish. Is this even possible?
It might be a simple coding error, it's been a long day.
I dont want to upload the image when the user presses the share button, as they might cancel the Activity View which means the uploaded image is sitting there not being used.
This is the code I currently have, but it returns nil before the image has uploaded and within the block it doesn't let me return nil for the errors:
- (id) activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
[self getShortUrlForUploadedImageWithCompletionHandler:^(NSString *shortUrl, NSError *error) {
if (!error) {
if ( [activityType isEqualToString:UIActivityTypeMail] ) {
NSString *shareString = #"Email content here using shortUrl";
return shareString;
} else {
return #"";
}
} else {
return #"";
}
}];
return nil;
}
-(void)getShortUrlForUploadedImageWithCompletionHandler:(NSString* (^)(NSString *shortUrl, NSError *error))completionHandler
{
NSData *imageToUpload = UIImageJPEGRepresentation(_image, 75);
AFHTTPClient *client= [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:kShareURL]];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"image", #"action",
#"simple", #"format",
nil];
NSMutableURLRequest *request = [client multipartFormRequestWithMethod:#"POST" path:nil parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFileData: imageToUpload name:#"image" fileName:#"temp.png" mimeType:#"image/jpeg"];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *response = [operation responseString];
NSLog(#"response: %#",response);
completionHandler(response, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if([operation.response statusCode] == 403){
NSLog(#"Upload Failed");
return;
}
NSLog(#"error: %#", [operation error]);
completionHandler(nil, error);
}];
[operation start];
}
-------- EDIT
I really could do with some help on this. My current work around is to upload the image when the user click my share button, before the Activity selection. So they could cancel the share and i'm left with a redundant uploaded image, or they could select Twitter which doesn't need the uploaded image.
I need to only upload the image if Email has been selected and I think the only place I can do this is in the Acticity Provider subclass.
Instead of implementing - (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType, try overriding the UIActivityItemProvider's - (id)item. This method will be called from the NSOperation's main method which is on a background thread.
As for waiting until the networking completion block triggers, I'd recommend you look into using a dispatch_semaphore. Here is an example:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"doing some work");
sleep(5);
NSLog(#"done with work");
dispatch_semaphore_signal(semaphore);
});
double delayInSeconds = 60.0;
dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
NSLog(#"waiting for background thread to finish");
dispatch_semaphore_wait(semaphore, waitTime);
NSLog(#"background thread finished, or took too long");
Make sure to only use this on a background thread though, otherwise you will block the main thread.

Resources