1

I have been reading up on Objectice-C blocks as I have been running into them more and more lately. I have been able to solve most of my asynchronous block execution problems, however I have found one that I cannot seem to fix. I thought about making an __block BOOL for what to return, but I know that the return statement at the end of the method will be executed before the block is finished running. I also know that I cannot return a value inside the block.

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {

    if ([identifier isEqualToString:@"Reminder Segue"]) {

        eventStore = [[EKEventStore alloc] init];

        [eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {

            if (!granted) {

                UIAlertView *remindersNotEnabledAlert;
                remindersNotEnabledAlert = [[UIAlertView alloc] initWithTitle:@"Reminders Not Enabled" message:@"In order for the watering reminder feature to function, please allow reminders for the app under the Privacy menu in the Settings app." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];

            //I would like to put a simple return NO statement here, but I know it is impossible
            }
        }];
    }

    return YES;
}

How do I create a simple return statement from a block?

Andrew
  • 479
  • 1
  • 5
  • 13
  • possible duplicate of [Method BOOL return from inside block](http://stackoverflow.com/questions/11632780/method-bool-return-from-inside-block) – rmaddy May 07 '14 at 00:45

3 Answers3

3

While the immediate idea might be to make your asynchronous request synchronous, that's rarely a good idea, and do to so in the middle of a segue, such as this case, is likely to be problematic. It's almost never a good idea to try to make an asynchronous method synchronous.

And, as smyrgl points out, the idea of "can't I just return a value from the block" is intuitively attractive, but while you can define your own blocks that return values (as Duncan points out), you cannot change the behavior of requestAccessToEntityType such that it returns a value in that manner. It's inherent in its asynchronous pattern that you have to act upon the grant state within the block, not after the block.

So, instead, I would suggest a refactoring of this code. I would suggest that you remove the segue (which is likely being initiated from a control in the "from" scene) and not try to rely upon shouldPerformSegueWithIdentifier to determine whether the segue can be performed as a result of a call to this asynchronous method.

Instead, I would completely remove that existing segue and replace it with an IBAction method that programmatically initiates a segue based upon the result of requestAccessToEntityType. Thus:

  • Remove the segue from the button (or whatever) to the next scene and remove this shouldPerformSegueWithIdentifier method;

  • Create a new segue between the view controllers themselves (not from any control in the "from" scene, but rather between the view controllers themselves) and give this segue a storyboard ID (for example, see the screen snapshots here or here);

  • Connect the control to an IBAction method, in which you perform this requestAccessToEntityType, and if granted, you will then perform this segue, otherwise present the appropriate warning.

    Thus, it might look something like:

    - (IBAction)didTouchUpInsideButton:(id)sender
    {
        eventStore = [[EKEventStore alloc] init];
    
        [eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
    
            // by the way, this completion block is not run on the main queue, so
            // given that you want to do UI interaction, make sure to dispatch it
            // to the main queue
    
            dispatch_async(dispatch_get_main_queue(), ^{
                if (granted) {
                    [self performSegueWithIdentifier:kSegueToNextScreenIdentifier sender:self];
                } else {
                    UIAlertView *remindersNotEnabledAlert;
                    remindersNotEnabledAlert = [[UIAlertView alloc] initWithTitle:@"Reminders Not Enabled" message:@"In order for the watering reminder feature to function, please allow reminders for the app under the Privacy menu in the Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
                    [remindersNotEnabledAlert show];
                }
            });
        }];
    }
    
Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Yes, typically I'd say triggering anything like this from a prepareForSegue or shouldPerformSegue method is a poor idea. Those methods are designed to pass any properties needed to the next view controller or to handle simple decision logic to proceed , it should not be used for much if anything else. I think the problem is that too many people get stuck on attaching segues to buttons in a Storyboard and don't realize it is trivially easy to trigger them programatically. – smyrgl May 07 '14 at 01:32
  • @smyrgl You completely misunderstand my point. My first bullet point entails the removal of the segue invoked by the control (and thus you would obviously completely eliminate this `shouldPerformSegue`, too). I'm saying you replace all of that with a basic `IBAction` method, that programmatically invokes `requestAccessToEntityType`, and that then calls `performSegueWithIdentifier` if and only if access has been granted. I've tried to clarify this point in my answer. – Rob May 07 '14 at 01:38
  • no I get what you are saying completely, I think you are missing what I am saying. I'm saying that the reason most people don't think about programatic segue firing like this is that they can't just connect a segue to a button in the Storyboard, they have to switch to a manual segue and fire it programatically via the IBAction as you mentioned. – smyrgl May 07 '14 at 01:41
  • 1
    Yes I know I was agreeing with you. I was referring to what the OP was doing. Sorry if I was unclear, I was just pointing out how some people get themselves into this situation and why I find there is very little use for directly triggering a segue from a Storyboard. – smyrgl May 07 '14 at 01:43
  • 1
    @Rob This worked great! I apologize for taking so long to mark your answer correct, as I got a bit frustrated with some programming issues and decided to take a break for a week. – Andrew May 15 '14 at 01:29
2

You CAN return a value from a block, just like from any function or method. However, returning a value from a completion block on an async method does not make sense. That's because the block doesn't get called until after the method finishes running at some later date, and by then, there is no place to return a result. The completion method gets called asynchronously.

In order to make a block return a value you need to define the block as a type that does return a value, just like you have to define a method that returns a value.

Blocks are a bit odd in that the return value is assumed to be void if it's not specified.

An example of a block that returns a value is the block used in the NSArray method indexOfObjectPassingTest. The signature of that block looks like this:

(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate

The block returns a BOOL. It takes an object, an integer, and a pointer to a BOOL as parameters. When you write a block of code using this method, your code gets called repeatedly for each object in the array, and when you find the object that matches whatever test you are doing, you return TRUE.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Why did someone downvote this? It's a perfectly reasonable answer. Anyway I just wanted to add as a bit of trivia that blocks returning a default of void can be traced to their origins as essentially anonymous c functions and c functions default to a return of void when not specified. You usually don't see a lot of cases for a return in a block except when they are used as part of a greater async process (like with a processing block for an async network operation) and I haven't seen a lot of patterns that use returns but maybe some people find different uses for them. – smyrgl May 07 '14 at 01:24
  • how is this answer the OP's question? he is not asking how to make block return BOOL – Bryan Chen May 07 '14 at 01:33
  • He asked about RETURNING a value from the block. You are right that it's probably not going to get the desired effect but that doesn't mean Duncan's answer was wrong, just that the OP asked the question the wrong way. Return means something and assigning a local variable outside a block is NOT the same as returning a value from a block. – smyrgl May 07 '14 at 01:34
  • There is very little context for what the OP is trying to accomplish here and Duncan provided him with an answer to his literal question which at the very least was informative. I provided an answer that will solve his problem but is a bad design choice, ultimately the correct answer is neither, the right answer is he needs to refactor his design outside the scope of what he has presented here. – smyrgl May 07 '14 at 01:36
  • this answer will be irrelevant if someone edit the question title to something what OP really asking for. you can't change the block signature. – Bryan Chen May 07 '14 at 01:44
  • Gents, the OP is asking how to return a value from an async completion block and have that result affect the code immediately following the async method. The completion block on an async method is called, er, asynchronously. Thus, the answer to the OPs question is "you can't get there from here." Another part of the reason why is that you can't add a return value to a block who's signature does not define one. Another reason that "you can't get there from here". – Duncan C May 07 '14 at 02:09
0

If you really want to make a block synchronous (although I question the validity of doing so) your best bet is to use a dispatch_semaphore. You can do it like this:

dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(0);

__block BOOL success;

        [eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
             success = granted;
             dispatch_semaphore_signal(mySemaphore);
        }];

dispatch_semaphore_wait(mySemaphore, DISPATCH_TIME_FOREVER);

However again I don't think you want to do this, especially in a segue as it will stall the UI. Your better bet is to rearchitect what you are doing so that you don't have a dependency on the async process being completed in order to continue.

smyrgl
  • 864
  • 6
  • 12
  • I agree that restructuring is probably the way to go, however, I have not been able to find any concrete examples on restructuring for a rather simple return statement. – Andrew May 07 '14 at 01:25
  • It depends entirely on what you are doing. Are you trying to prevent the segue from completing before the block is or are you just trying to update some global variable or trigger a follow on action? If so look into using NSNotificationCenter and broadcasting an event when it is done, then having some other class handle the follow on logic. Forget trying to return a value directly, thing about the chain of events you want to have happen. – smyrgl May 07 '14 at 01:27