0

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.

7
  • 2
    Just a hinch: is the unspecified WebServicesManager dispatching it's callback to the main thread, which is waiting for the semaphore? Commented Jan 13, 2016 at 10:45
  • Hmmm, that does make sense, I will check. Commented Jan 13, 2016 at 10:47
  • Jan is correct. But, for the record, this is a very, very bad practice. You're going to block the thread, which you should never do (if you're unlucky in your timing, this can end up having the watchdog process kill your app, it's a horrible UX, etc.). You should refactor your code that calls this to adopt asynchronous patterns. If you need help with that, edit your question and show us how you're calling getUserInformation and how the flow continues, and we can offer further counsel. Commented Jan 13, 2016 at 10:48
  • Looks fine to me and tested out okay too. Only difference is I had some code to fill the if..else with just incase the optimisation decided to skip the if..else. I'll give it another go without in a mo and let you know. Commented Jan 13, 2016 at 10:50
  • if getUserInformationWithCallback is executed in the same thread, it will cause dead lock and make your app freeze. You should be careful with using dispatch_semaphore_t Commented Jan 13, 2016 at 10:53

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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

3 Comments

Rob, I am already using this solution with other calls in the application. It was my desire to find an alternate solution that brought semaphores to my attention. For now, I have updated my code to work with a completion handler. I came across another answer, from you, on another question where you demonstrated how two threads communicated with semaphores, I tried using that but still couldnt get it to work. How would I go about updating the code, in the question, to use semaphores along with different threads and not lock up?
Yeah, the semaphore approach can easily deadlock if you're not careful. You can probably refactor the web services API to avoid this problem, but I personally wouldn't waste another second trying to figure out how to fix your deadlock issue, solely for the purposes of writing code to block the main thread. I know that just returning the value immediately is so seductive (it seems so logical), but for a myriad of reasons, it's a horrible practice with negative implications. Seriously, don't do it.
Refactoring the WebServices class isnt an option that I would be willing to consider, it will have far reaching consequences and might herald the end of the word as we know it. Letting this go till I "really really" need to use semaphores somewhere :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.