I'm having a problem when I want to execute a code inside my dispatch_after block.
First of all, I'm calling a UIActivityIndicator when a button is pressed in order to show it in screen and after the uiactivityindicator starts runnning I want to execute a server call, when I get a response from the server I return that value.
The problem is: When I call my UIAtivityIndicator to run and after that I make my server call, the UIActivityIndicator doesn't show in screen even when the [UIActivityIndicatorInstance startAnimating]; was called and after that the server operation was called.
So I decided to use a dispatch_after in order to wait a certain time after de [UIActivityIndicatorInstance startAnimating]; It works whe I do this, the problem becomes when I have to return the value, so for that reason a use dispatch_semaphore to tell me when the operation has finished and then return the value.
The big problem here is that the dispatch_after is not called.
This is my code, I appreciate you can help me with this problem or some other solution you have in mind.
The main idea that I want to accomplish is that I want to show an UIActivityIndicator while the server operation is executing and when it finishes I want to return that value in the same method.
- (BOOL)getUserSatatus {
// This is when the UIActivityIndicator is starts running
[Tools startActivityIndicator];
double delayInSeconds = 0.5;
// This is used to save server response.
__block BOOL serverResponse;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t executionTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
// I want to execute the server call after a perios of time in order to show first de indicator on screen
dispatch_after(executionTime, dispatch_get_main_queue(), ^{
NSLog(#"This is where the server will call");
// This is when I perform the service call and it returns a values that is
// assigned to server response.
serverResponse = [_backendManager getStatus];
// This is the signal for the semaphore in order to execute the next lines.
dispatch_semaphore_signal(semaphore);
});
// Wait until the signal in order to execute the next line.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return serverResponse; // Here will be the server return response.
}
You say:
The big problem here is that the dispatch_after is not called.
Yes, that's because you're blocking the main thread with dispatch_semaphore_wait, so the dispatch_after never has a chance to run on the main thread and you're deadlocking.
We can walk you through ways to get around this, but you really shouldn't have synchronous network calls or semaphores in your code at all (for a myriad of reasons, not just for your activity indicator and for solving your deadlocking issue).
You should remove these synchronous network requests, remove the dispatch_after, and remove the semaphores. If you do all of that, and instead follow asynchronous patterns (like using completion blocks), your activity indicator view stuff will then work properly and you won't have any deadlock either.
The correct answer is to refactor the "back end manager" to perform its requests asynchronously (with completion blocks) and then use completion block pattern with getUserStatus method, too.
For example, let's say you fixed getStatus of the _backendManager to behave asynchronously:
- (void)getStatusWithCompletion:(void (^)(BOOL))completion {
NSMutableURLRequest *request = ... // build the request however appropriate
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
BOOL status = ...; // parse the response however appropriate
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(status);
});
}];
[task resume];
}
Then you can refactor the getUserStatus from your question to also take a completion handler:
- (void)getUserStatusWithCompletion:(void (^)(BOOL))completion {
// This is when the UIActivityIndicator is starts running
[Tools startActivityIndicator];
[_backendManager getStatusWithCompletion:^(BOOL status){
[Tools stopActivityIndicator];
if (completion) completion(status);
}
}
And then the code that needs to get the user status would do something like:
[obj getUserStatusWithCompletion:^(BOOL success) {
// use `success` here
}];
// but not here
Related
I have a network operation that invokes a block when it's done:
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// call this method's completion handler
For my app to begin, I'd like to run this along with some UIView block animations to start an app. Before the app can proceed, both the user animations must be seen and the network operation must complete. I'd prefer to have these async things happen at the same time, and I've tried something like this:
__block BOOL animationsDone=NO, networkDone=NO;
[self doUIViewBlockAnimations:^(BOOL finished) {
animationsDone = YES;
if (networkDone) [self startTheApp];
}];
[self doNetworkThing:^(id result) {
networkDone = YES;
if (animationsDone) [self startTheApp];
}];
This seems to run fine, and I think it is safe because these blocks must both run on the main queue, so I think they cannot run simultaneously. But I'm a little shaky on this stuff (e.g. like what nonatomic does, even though I put it on every property).
Is there any risk that both of these blocks complete but somehow the state of the BOOLs gets mixed up and the app doesn't start? That could be a hard bug to figure out later on.
Needed some advice/review on possible downsides of using a dispatch group inside another group, if it could lead to a race condition/deadlock or just wrong practice.
1) Can a dispatch_group_enter exist inside the scope of another group? I could not find an example from Apple following such practice. Remember, secondCall needs to happen after firstCall. There is a dependency. Thoughts?
2) What would be a good design to execute a thirdCall - which again depends on result of firstCall result. But agnostic of the completionHandler timing i.e. can happen later and doesn't need to wait for completionHandler to finish.
Here's a simplified example of the completion handler incorporating 3 calls -
-(void)someMethod:(void (^)(NSError *error))completionHandler {
dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_group_enter(serviceGroup);
__typeof__(self) __weak weakSelf = self;
[self.obj firstCall completion:^(NSError *firstError) {
__typeof__(self) strongSelf = weakSelf;
// Second Call
if (!firstError.code) {
dispatch_group_enter(serviceGroup);
[strongSelf.obj secondCall completion:^(void) {
dispatch_group_leave(serviceGroup);
}];
}
// Third call
if (!firstError.code) {
[strongSelf executeThirdCall];
}
dispatch_group_leave(serviceGroup);
}]; // Closing block for first call.
dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
if (completionHandler) {
completionHandler(error);
}
});
}
Some classic examples of dispatch groups can be found in this answer.
I can't think of any issues with this code.
However I am not sure you need dispatch groups at all for this example.
You are executing three requests. Request 2 and Request 3 both depend on the result of the Request 1. You need to call the function's completionHandler when Request 2 is finished. Can't you do it into the completion handler of Request 2?
I have four api calls to make. They should be in following order:
apiSyncDataToCloud;
apiSyncImagesToServer;
apiDeleteDataFromCloud;
apiSyncDataFromCloudInBackground;
Each one of them is to be called irrespective of the fact that previous one finishes successfully or fails.
Also, each one of them have success and failure completion blocks.
In success completion block database is updated.
All this process has to be performed in background and has to be done a no of times.
Api calls are of course performed in background but once a call completes database update is performed on main thread thereby freezing the app.
So, I went with several solutions:
Tried following code:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
[self apiSyncDataToCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncImages];
}];
[queue addOperationWithBlock:^{
[self apiDeleteDataFromCloud];
}];
[queue addOperationWithBlock:^{
[self apiSyncDataFromCloudInBackground];
}];
But this only guarantees that api method calls will be performed in order. But their result follows no specific order. That is, method calls will be in the order specified but success block of apiSyncImagesToServer may be called before success block of apiSyncDataToCloud.
Then I went with following solution:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self apiSyncDataToCloud];
});
and in the success and failure blocks of apiSyncDataToCloud I have called apiSyncImagesToServer. This too did'nt work.
Now I am simply going with my last solution. I am just calling apiSyncDataToCloud.
In success completion block this method first updates the database and then calls other api.
In failure completion block this method simply makes the api call without updating the database.
For example-
structure of apiSyncDataToCloud is as follows:
-(void)apiSyncDataToCloud{
NSLog(#"method 1");
NSMutableDictionary *dicDataToBeSynced = [NSMutableDictionary dictionary];
dicDataToBeSynced = [self getDataToBeSynced];
if (dicDataToBeSynced.count!=0) {
if ([[StaticHelper sharedObject] isInternetConnected]) {
[[ApiHandler sharedObject] postRequestWithJsonString:API_SYNC_DATA_TO_CLOUD andHeader:[UserDefaults objectForKey:kAuthToken] forHeaderField:kAccessToken andParameters:dicDataToBeSynced WithSuccessBlock:^(NSURLResponse *response, id resultObject, NSError *error) {
NSLog(#"Data synced successfully to server");
[self updateColumnZSYNC_FLAGForAllTables];//updating db
[self apiSyncImagesToServer];//api call
} andFailureBlock:^(NSURLResponse *task, id resultObject, NSError *error) {
NSLog(#"Data syncing to cloud FAILED");
[self apiSyncImagesToServer];//simply make api call without updating db
}];
}
}else{
[self apiSyncImagesToServer];make api call even if no data to be synced found
}
}
Similary, inside apiSyncImagesToServer I am calling apiDeleteDataFromCloud.....
As a result my problem remained as it is. App freezes when it comes to success block updating db, downloading images...all operations being performed on main thread.
Plz let me know a cleaner and better solution.
You can create your own custom queue and call request one by one.
i.e.
dispatch_queue_t myQueue;//declare own queue
if (!myQueue) {//check if queue not exists
myQueue = dispatch_queue_create("com.queue1", NULL); //create queue
}
dispatch_async(myQueue, ^{[self YOUR_METHOD_NAME];});//call your method in queue block
If you want update some UI after receiving data then update UI on main Thread.
1) Better to use AFNetworking for this kind of situations. Because AFNetworking provides better way to handle Main & Background Threads.
AFNetworking supports success and failure blocks so you can do one by one WS Api calls from success and failure of previous WS Api call. So during this time period show progress HUD. Success of last API then update DB and hide progress HUD.
2) If you need to use NSOperationQueue and NSInvocationOperation
and follow this link. https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift
Api calls are of course performed in background but once a call
completes database update is performed on main thread thereby freezing
the app.
Then why not perform it in a separate queue?
Try using
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//your code
});
to perform time-consuming tasks and
dispatch_async(dispatch_get_main_queue(), ^{
//your code
});
to only update UI.
I have a method in an iOS app that is supposed to return a bool value depending upon whether or not a web call succeeds.
The web call is structured in a way such that it takes a block as a callback parameter and that callback is called when the web call has a result. Based on that result my method needs to return a True/False value.
So, I need to stop execution from progressing any further without first having a result to return.
I am trying to achieve this via semaphores, after looking at some examples that others have shared, but the callback is never called, if I remove the semaphore then the callback is always called.
What am I missing here?
+ (BOOL)getUserInformation {
__block BOOL flag = false;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[WebServicesManager sharedManager] getUserInformationWithCallback:^(NSInteger statusCode, NSString *response, NSDictionary *responseHeaders, id obj, NSError *error) {
if (error) {
//Handle error case and perform appropriate cleanup actions.
}
else
{
//Save user information
flag = true;
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return flag;
}
I put a break point on if(error) to check if the callback gets called, it doesnt, unless I remove the semaphore.
I could give this method its own callback block or I could give the containing class a delegate and achieve what I need but I would really like to make this approach work.
The WebServicesManager is probably dispatching it's block on the same thread the semaphore is waiting on.
As #Rob is correctly mentioning in the comments, this is most likely not a good idea to do on the main thread; rather make use of the asynchronous model and not block the main thread for possibly minutes until the connection may time out under certain circumstances, freezing your UI.
You are undoubtedly deadlocking because you're using semaphore on same thread to which the web services manager (or the API that that is using) dispatches its completion handler.
If you want a rendition that avoids the deadlock scenario, but also avoids the pitfalls of blocking the main thread, you can do something like:
+ (void)getUserInformation:(nonnull void (^)(BOOL))completionHandler {
[[WebServicesManager sharedManager] getUserInformationWithCallback:^(NSInteger statusCode, NSString *response, NSDictionary *responseHeaders, id obj, NSError *error) {
if (error) {
completionHandler(false);
} else {
//Save user information
completionHandler(true);
}
}];
}
Then, rather than doing something like:
BOOL success = [YourClass getUserInformation];
if (success) {
...
}
You can instead do:
[YourClass getUserInformation:^(BOOL success) {
if (success) {
...
}
}];
// but do not try to use `success` here ... put everything
// contingent upon success inside the above completion handler
I do a post request using Async
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
I have the call backs for when the connection has stopped and in here I call my method for stopping the UIActivityIndicatorView
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSLog(#"Connection finish");
[self stopAnimatingSpinner];
}
Heres the stop animating method (I have tried a combination and all of the below stop, remove hide methods
-(void)stopAnimatingSpinner{
[submittingActivity stopAnimating];
submittingActivity.hidden = YES;
[submittingActivity removeFromSuperview];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
Now the problem is sometimes it stops sometimes it doesnt and is very random. If I move the stopping of the activity to the finish parsing of my data instead of relying on the connection callbacks the behaviour is exactly the same. Sometimes they stop sometimes they don't?
The only thing I can think of is that the connection is blocking the main thread but why would it work sometimes and not others?
Two principal problems:
If you use the sendAsynchronousRequest: (etc.) method of NSURLConnection, then only the completion block will be called upon completion, and the connectionDidFinishLoading: delegate method isn't. That's another API.
If you're updating the UI, you should always do so on the main thread. Otherwise your program invokes undefined behavior. So wrap the code which stops the animation in a block that's dispatched on the main thread:
dispatch_sync(dispatch_get_main_queue(), ^{ [self stopAnimatingSpinner]; });
If you are invoking NSURLConnection from main thread, then your completion handler also will be called from main thread. Hence, you should not invoke dispatch_sync when stopping the spinner.