1

I'm trying to do a share operation where I call a function with async block but in my next if statement I need to get the value which is completed in the block to continue. This is my code which will highlight more detail. I heard about NSLock and tried using it but it didnt work, may be I'm doing something lock, I'm not much familiar with locks.

-(void) shareOperation
{
__block NSString *resultText;
BOOL continueSharing;
 NSLock *conditionLock=[[NSLock alloc] init];
         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            [conditionLock lock];
        [self performSomeAsynchronousOperation completionBlock:^(NSError *tError, bool status)
         {
             dispatch_async(dispatch_get_main_queue(),
                            ^{

                                if (status)
                                {
                                    resultText = @"Operation completed. Would you like to continue?";

                                }
                                else
                                {
                                    resultText = @" Operation failed. Would you still like to continue?";
                                }
                                UIAlertView *result = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:@"Cancel" otherButtonTitles:[NSArray arrayWithObjects:@"OK",nil] onDismiss:^(int buttonIndex)
                                                       {
                                                           NSLog(@"selected button index: %d",buttonIndex);
                                                           if (buttonIndex == 0)
                                                           {
                                                               continueSharing=YES;
                                                               [conditionLock unlock];
                                                               NSLog(@"We are continuing sharing :)");
                                                           }
                                                       }onCancel:^{
                                                           continueSharing=NO;
                                                            [conditionLock unlock];
                                                           NSLog(@"cancelled");
                                                       }]; [result show];

                            });
         }];
        });
    }


    //should continue only after earlier if block finishes, ie after getting the continueSharing value

    if (continueSharing)
    {
        [self performSomeAnotherAsynchronousOperation];
    }

}
Francis F
  • 3,157
  • 3
  • 41
  • 79

2 Answers2

5

Rather than using locks (which are only designed to ensure that there is not simultaneous access to some shared resource), you could use semaphores (which are designed so that one thread can wait for a signal from another thread, which is needed here).

So, you should create a semaphore:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

The alert view completion blocks would signal that semaphore:

dispatch_semaphore_signal(semaphore);

And where you want to wait for that signal (before performSomeAnotherAsynchronousOperation):

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

I tweaked your code a bit, but most notably, changed it so that it would not block the main queue (which you never want to do) by making sure the dispatch_semaphore_wait is done in a background queue. Also note that the dispatch_semaphore_signal is not inside the if statement. This resulted in:

- (void)shareOperation
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __block BOOL continueSharing = NO;
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

        [self performSomeAsynchronousOperationWithCompletionBlock:^(NSError *tError, bool status){
            dispatch_async(dispatch_get_main_queue(),^{
                NSString *resultText;

                if (status)
                    resultText = @"Operation completed. Would you like to continue?";
                else
                    resultText = @"Operation failed. Would you still like to continue?";

                UIAlertView *alertView = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:@"Cancel" otherButtonTitles:@[@"OK"] onDismiss:^(int buttonIndex) {
                    NSLog(@"selected button index: %d",buttonIndex);
                    if (buttonIndex == 0) {
                        continueSharing = YES;
                        NSLog(@"We are continuing sharing :)");
                    }
                    dispatch_semaphore_signal(semaphore);
                } onCancel:^{
                    dispatch_semaphore_signal(semaphore);
                    NSLog(@"cancelled");
                }];

                [alertView show];
            });
        }];

        // should continue only after earlier if block finishes, ie after getting the continueSharing value

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        if (continueSharing)
            [self performSomeAnotherAsynchronousOperation];
    });
}

Even better, you should not use any blocking mechanism like semaphores (certainly not on the main queue), but rather one should simply have the completion block for the alert view initiate the next step of the process directly, itself. I understand that you say that this is not practical in your scenario, but it is generally the correct way to handle these scenarios.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Option 3 is not an option for me, as there are many nested operation in my actual code, this code was just to show the problem, i tried first option but got [NSLock lock]: deadlock , i'm not familiar with semaphore, so now looking into how to implement that. Thanks for your reply. – Francis F Jan 17 '14 at 11:46
  • I tried semaphore but same problem UI get hang, I think there is different problem, actually my [self performSomeAsynchronousOperation completionBlock...] method is supposed to bring a pop-up and when I click on the Ok button on that pop-up it will return status then only it will go to show alertview. I think the pop-up is also in wait mode as it never shows up. If so how should I tackle this situation? – Francis F Jan 17 '14 at 12:00
  • How do I do that?, I tried adding my initial function inside dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); but that didnt help – Francis F Jan 17 '14 at 12:34
  • @Gamerlegend OK, to step back: 1. Locks really are not a suitable solution, so I've completely removed that from my answer. Simple `NSLock` doesn't work, but it doesn't makes sense to try to remedy it. 2. Given your comments, I tested my revised semaphore answer and it appears to work, but I included a more complete code sample for your review. I cleaned a few things in your code sample, so take a look. – Rob Jan 17 '14 at 14:22
  • 3. In terms of your `performSomeAsynchronousOperation` competing alert view question, it's hard to say without details about how `performSomeAsynchronousOperation` works, specifically when the completion handler is called in relation to the alert it shows. Is the completion handler called before or after you dismiss the alert that this `performSomeAsynchronousOperation`'s alert view is show/dismissed? – Rob Jan 17 '14 at 14:22
  • The performSomeAsynchronousOperation is written in general in a different share class, I'm currently doing a twitter share in that function so it is supposed to bring the twitter pop-up for posting. Once the post is shared or cancel in the pop-up the completion handler will return a true or false status. – Francis F Jan 17 '14 at 19:40
  • @Gamerlegend Then I would have thought that the above semaphore would do the job. This is a very common pattern, though care must be taken in the precise placement of the `signal` and `wait` calls. – Rob Jan 17 '14 at 22:07
  • Thanks I will check that out, may be I'm doing something wrong. Anyways thanks for all of your effort, I'll mark it as best answer. – Francis F Jan 19 '14 at 04:44
  • I'm not quite sure about the problem but when i Commented out dispatch_async(dispatch_get_main_queue().. in your code it worked just fine but later I had to uncomment it back to make it work again. – Francis F Jan 20 '14 at 05:33
  • I figured out the issue, we have to get the main queue before calling the alert. I have updated the answer so that it may help others. – Francis F Jan 21 '14 at 08:35
0

You can use the delay block

 dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC);

    dispatch_after(delayTime, dispatch_get_main_queue(), ^(void){

    })
Bug
  • 2,576
  • 2
  • 21
  • 36
Divya
  • 818
  • 8
  • 10