Search code examples
objective-cshort-circuitingevaluate

Objective-C && doesn't short circuit for !var


Try running this:

UIView *testView = nil;

NSLog(@"Take 1");
NSString *message = @"view doesn't exist";
if (!testView && !testView.subviews) {
   message = @"this message should never appear" ;
}
NSLog(message);


NSLog(@"Take 2");
message = @"view doesn't exist";
if (testView != nil && testView.subviews != nil) {
    message = @"this message should never appear" ;
}
NSLog(message);

NSLog(@"Take 3");
message = @"view doesn't exist";
if (!testView) {
    if (!testView.subviews) {
        message = @"this message should never appear" ;
    }
}
NSLog(message);

NSLog(@"Take 4");
message = @"view doesn't exist";
if (testView != nil) {
    if (testView.subviews != nil) {     
        message = @"this message should never appear" ;
    }
}
NSLog(message);

Output I get is:

Take 1
this message should never appear
Take 2
view doesn't exist
Take 3
this message should never appear
Take 4
view doesn't exist

Why doesn't Obj-C short circuit for !testView (in Take 1)?

Why does it go into !testView when testView is clearly nil in Take 3?

Should I not be testing the function of a nil object (e.g. when I test for subviews)?


Solution

  • The output you see is correct and the short-circuit behavior is working correctly too.

    When boolean expressions are evaluated the result is considered to be true if the expression is not 0 or false if it is 0. So everywhere where you have if (something) you can read this as if (something != 0). The ! operator is the negation, so if you expand it you get the following for your first case: !(testView != 0) && !(testView.subviews != 0). The double negation can be removed and you get (testView == 0) && (testView.subviews == 0) which obviously is true (nil is 0 too).

    There the short-circuiting is also correctly applied, you just can't see it. To prove that you could use a little wrapper function for your tests:

    id testFunc( id value ) {
        NSLog(@"testFunc: %@", value );
        return value;
    }
    

    And use that in your tests: if (!testFunc(testView) && !testFunc(testView.subviews))

    To make it short, your assumptions about the boolean not operator ! are wrong. It goes into if (!testView) because testView is nil.