Reputation: 1732
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
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
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