snowbound
snowbound

Reputation: 1732

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

Upvotes: 1

Views: 178

Answers (2)

joerick
joerick

Reputation: 16448

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.

Upvotes: 0

anon
anon

Reputation:

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.

Upvotes: 1

Related Questions