1

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)?

snowbound
  • 1,692
  • 2
  • 21
  • 29
  • 1
    You have confused yourself. Since `testView` is `nil`, `!testView` is true. So is `!testView.subviews`. Among other things, the logic of Take 3 and Take 4 are clearly opposite, so you should expect the opposite result. Lo and behold, you get the opposite result. – Ken Thomases Feb 15 '15 at 14:11
  • Thanks @KenThomases, I've removed the culprit case. – snowbound Feb 16 '15 at 12:26

2 Answers2

1

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.

Sven
  • 22,475
  • 4
  • 52
  • 71
  • In other words @Sven, when checking for `nil`. I should write `(var != nil)` but never `(!var)`?? – snowbound Feb 16 '15 at 12:25
  • Those both check for `nil` (in effect) but with the opposite sense. `(var == nil)` is the same as `(!var)`. `(var != nil)` is the same as `(var)`. When you want to do what in English would be written "if testView exists", you can do either `if (testView)` or `if (testView != nil)`. When you want to do "if testView does not exist", you can do `if (!testView)` or `if (testView == nil)`. – Ken Thomases Feb 16 '15 at 13:58
0

There's nothing strange going on here. I rewrote your code, substituting testView for nil and testView.subviews for nil (messages to nil return nil).

NSLog(@"Take 1");
NSString *message = @"if clause was false";
if (!nil && !nil) { // if(true && true)
   message = @"if clause was true" ;
}
NSLog(message); // outputs "if clause was true"


NSLog(@"Take 2");
message = @"if clause was false";
if (nil != nil && nil != nil) { // if (false && false)
    message = @"if clause was true" ;
}
NSLog(message); // outputs "if clause was false"

NSLog(@"Take 3");
message = @"if clause was false";
if (!nil) { // if (true)
    if (!nil) { // if (true)
        message = @"if clause was true" ;
    }
}
NSLog(message); // outputs "if clause was true"

NSLog(@"Take 4");
message = @"if clause was false";
if (nil != nil) { // if (false)
    if (nil != nil) { // if (false)
        message = @"if clause was true" ;
    }
}
NSLog(message); // outputs "if clause was false"

If you want to know if the view exists, you can use something like:

if (testView) {
    NSLog(@"View exists a.k.a. testView != nil");
} else {
    NSLog(@"View doesn't exist a.k.a. testView == nil");
}

I'd recommend just using if (testView) and if (!testView) to check for the existence of objects. This is the standard in the Objective-C community and reads more clearly.

joerick
  • 16,078
  • 4
  • 53
  • 57