1

I have simple app with two views: the first view has a button, and when you press it you get a modal segue to another view. As you know, the controller popped out with a modal segue doesn't have a "Back" button, so you probably write delegates. I want to use blocks for more compact code. Firstl I did the following.

Declare block in "Second" view controller:

typedef void (^SecondViewControllerCompletionBlock)(BOOL success);

@interface SecondViewController : UIViewController

@property (nonatomic, copy) SecondViewControllerCompletionBlock CompletionBlock;

I wrote code in a "Done" button's action (which is the button on top of the UINavigationController):

- (IBAction)doneBarButton:(id)sender {

    if (self.CompletionBlock !=nil){
        self.CompletionBlock (YES);
    }

}

Preparing in the "First" view controller for segue:

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{



    if ([segue.identifier isEqualToString:@"ShowDetail"]){

     __weak SecondViewController *controller = segue.destinationViewController;
     controller.CompletionBlock = ^(BOOL success){

         if (NO){
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    };

}

So, everything works just fine; I can close the menu by pressing the done button. But what if I want something other than BOOL when declaring the block? I tried using an int.

Declaring the block:

typedef void (^SecondViewControllerCompletionBlock)(int success);

@interface SecondViewController : UIViewController

@property (nonatomic, copy) SecondViewControllerCompletionBlock CompletionBlock;

Changing the code in the doneBarButtonItem: action method:

- (IBAction)doneBarButton:(id)sender {

    if (self.CompletionBlock !=nil){
        self.CompletionBlock (1);
    }

}

In this method when I wrote an if statement, I placed 2 in round brackets as the argument, but, my point was, if you put here anything except 1 (which I pass when configuring the action method), the statement should not execute, so, controller should not close. But it does, in all cases, if I put 1 in if(1) statement, or 2, or anything else.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

    if ([segue.identifier isEqualToString:@"ShowDetail"]){

        __weak SecondViewController *controller = segue.destinationViewController;
        controller.CompletionBlock = ^(int success){

            if (2){
               [self dismissViewControllerAnimated:YES completion:nil];
            }
        };

}

I might be missing something obvious. Can you help me fix this? It would be nice to have not only BOOL for changing actions.

jscs
  • 63,694
  • 13
  • 151
  • 195
Evgeniy Kleban
  • 6,794
  • 13
  • 54
  • 107
  • 2
    You're not _testing the argument_. You just have a literal value in the `if`'s condition, in both versions. – jscs Nov 02 '13 at 19:05
  • In first version when i use if(NO), my screen does not close. When i trying to do this with numbers i can't achieve my point – Evgeniy Kleban Nov 02 '13 at 19:10
  • That's because `if(NO)` always fails, just as `if(0)` would. And `if(1)` or `if(2)` will always succeed. None of them have any connection to your variable. – jscs Nov 02 '13 at 19:18
  • What should i do to switch cases using numeric variables instead of BOOL then? – Evgeniy Kleban Nov 02 '13 at 19:25
  • The same thing you need to do for a `BOOL`; use the Block's argument, not a literal value, in the `if` test. – jscs Nov 02 '13 at 19:26
  • 4
    @EvgeniyKleban - What Josh is trying to tell you is you need to test the value in the *variable*. So with your first example you wanted `if(!success)` and in the second `if(success == 1)`, or something like that. Given you've already written `if(self.CompletionBlock !=nil)` this looks like a case of too little sleep. Take a break! – CRD Nov 02 '13 at 19:28

1 Answers1

1
if (2) {
    ...
}

makes little sense. In C (and Objective-C) any non-zero value evaluates to true, so the test you are making is roughly equivalent to

if (YES) {
    ...
}

which, obviously, always passes.

What you really want is to test the argument against that value:

controller.CompletionBlock = ^(int success){
    if (success == 2){
        [self dismissViewControllerAnimated:YES completion:nil];
    }
};

Finally, a stylistic remark: all variables should start with a lowercase letter and block variables make no exception. CompletionBlock should be named completionBlock, instead.

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • Yeah thanks, a lot of useful information here. Now its working – Evgeniy Kleban Nov 02 '13 at 19:59
  • "anything different from 0 evaluates to false" - this is not correct, first you meant true, second is that some value that has lowest bytes equal to the number of signed char at zeroes, it will return false – Dvole Nov 02 '13 at 20:05
  • @Dvole, yes I meant true, thanks. I'm not sure I follow the rest of your comment, you mean bits instead of bytes, don't you? – Gabriele Petronella Nov 02 '13 at 20:08
  • Yes, I had that situation when we had some NSString and checked "if (string)" and very rarely this would return false with string there. Then we figured out that it casts whatever you passed in as signed char and if all bits of that portion are zeroes you get false – Dvole Nov 02 '13 at 20:10
  • 1
    Weird, are you absolutely positive that was the issue? Still my statement is correct (a part from the obvious typo). Anything non-zero evaluates to true and your case doesn't make a difference. If for some reason the highest bits were truncated, you were indeed evaluating zero to false, as expected. That being said, such discussion is not relevant in the context of the OP's issue, which is much more trivial than that. – Gabriele Petronella Nov 02 '13 at 20:16
  • 3
    I don't think this "lowest byte" and "cast to signed char" claims are correct. Anything different from 0/NULL is considered "true". – Martin R Nov 02 '13 at 20:20
  • 1
    @Dvole: You're mistaken. Any set bit will result in an object pointer evaluating to "true" in an `if`'s condition: https://gist.github.com/woolsweater/7283371 Some info here on SO in [When does ObjC implicitly cast to BOOL?](http://stackoverflow.com/q/14529781) – jscs Nov 02 '13 at 20:58
  • @JoshCaswell nice snippet. Thanks for the clarification. – Gabriele Petronella Nov 02 '13 at 21:11
  • @JoshCaswell from that answer - Now this can break, since BOOL is just typedeffed to signed char, which is 8 bit on iOS. Thus, if foo, which is a (32-bit) pointer, gets truncated to 8 bits, and the lower 8 bits of the pointer were all zero, but the pointer itself wasn't nil, this will incorrectly report false. – Dvole Nov 02 '13 at 21:36
  • 2
    @Dvole read the answer carefully. It happens if you **explicitly** cast `foo` to `(BOOL)`. It doesn't happen implicitly as you are suggesting, so `if (foo) {...}` is a safe way of checking whether `foo` is `nil`. – Gabriele Petronella Nov 02 '13 at 21:37
  • 3
    @Dvole - The confusion is understandable, until that is you remember we're talking about the C family of languages... In these `if` does **not** take a boolean expression at all, rather it takes an expression which is tested for being unequal to the zero of the type of the expression. So pointers are compared to `nil`, etc. – CRD Nov 03 '13 at 00:09