Does Objective-C use short-circuit evaluation?
Asked Answered
C

4

18

I tried something along the lines of:

if(myString != nil && myString.length) { ... }

And got:

-[NSNull length]: unrecognized selector sent to instance

Does Objective-C not short-circuit after the first condition fails?

Cyrilla answered 13/1, 2010 at 22:23 Comment(1)
Nope. NSNull and nil are not the same thing. Not at all.Illustrational
D
30

Objective-C does support short-circuit evaluation, just like C.

It seems that in your example myString is NSNull and not nil, therefore myString != nil is true.

NSNull is a singleton and is used to represent nil where only objects are allowed, for example in an NSArray.

Btw, normally, people write if (!myString && myString.length == 0). Comparing to nil is quite ugly. Also, I'd compare the length to 0. That seems to be more clear.

Dalessio answered 13/1, 2010 at 22:28 Comment(7)
Thanks for the tip. Im still trying to align myself with all the syntax & naming conventions.Cyrilla
I wouldn't say people 'normally' don't compare to nil. I've seen it done both ways with equal frequency. The argument being that you can read it as 'if myString is not nil' as opposed to 'if not myString'.Obstruent
Actually you're incorrect comparing to nil via (nil == variable) is the correct way to insure that a message is not send to a nil object.Kalpa
@Inturbidus: I'm not sure I understood what you meant. If you think one should use if (nil == myString) instead of if (!myString) you're wrong. nil is defined as 0 and therefore the two comparisons are exactly the same.Soler
You're correct that nil is simply a wrapper for the int 0, but right out of the O'Reilly iOS 4 book, page 45. Paraphrased, it is a better practice to use nil != mystring to prevent the compiler allowing a mystring = nil mistake which compiles fine but causes a crash. We all use (!mystring), but we should be teaching good practices where applicable.Kalpa
Technically, you could just do if(myString.length) {...} and leave it at that, because if myString is a nil object and you send a length message to it, you will get nil anyway.Imbroglio
if statements are only for evaluating boolean expressions. myString == nil is a boolean expression. myString isn't a boolean expression, so it shouldn't be used in an if as far as correct code goes, though due to C language implementation this does work.Hovel
S
10

Objective-C is a strict superset of C.

Because C supports short-circuit evaluation, Objective-C does as well.

Santonin answered 13/1, 2010 at 22:28 Comment(3)
NString* str = expressionThatReturnsStrOrNil() || @""; does not work. This is short-circuit evaluation not working.Discombobulate
@PedroMorteRolo: Of course it works. What result do you expect? (Keep in mind that the result of the || operator is either 1 or 0, never anything else).Santonin
You are right. I fell in the illusion that C had the same semantics as Ruby.Discombobulate
S
3

What is NSNull defined as? If it is an object that is supposed to represent nothing, than it would not be nil. in other words, NSNull and nil aren't the same.

Slowly answered 13/1, 2010 at 22:28 Comment(0)
G
0

If you have an NSNull somewhere, you are probably either using a JSON parser or CoreData. When a number in CoreData is not set, CoreData will give you back NSNull - possibly the same goes for NSString values in CoreData too.

Similarly, you can have empty elements in JSON returned from a server and some parsers will give you that as an NSNull object. So in both cases, you have to be careful when you are using values since the thing you thought was an NSString or NSNumber object is really NSNull.

One solution is to define a category on NSNull that simply ignores all non-understood messages sent to the object, as per the code below. Then the code you have would work because NSNull.length would return 0. You can include something like this in your project .pch file, which gets included in every single file in your project.

// NSNull+IgnoreMessages.h
@interface NSNull(IgnoreMessages) 
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
@end

//NSNull+IgnoreMessages.m
#import "NSNull+IgnoreMessages.h"
@implementation NSNull(IgnoreMessages)
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ( [self respondsToSelector:[anInvocation selector]] )
      [anInvocation invokeWithTarget:self];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *sig=[[NSNull class] instanceMethodSignatureForSelector:aSelector];
        // Just return some meaningless signature
    if(sig==nil)
      sig=[NSMethodSignature signatureWithObjCTypes:"@^v^c"];

    return sig;
}
@end
Galsworthy answered 14/1, 2010 at 1:1 Comment(7)
That's just waiting to fail. It's important to know when you're dealing with NSNull instead of nil, your code makes that less likely to happen. In the question's example there's an error message containing NSNull, you wouldn't see that with your code and if (myString) would still evaluate to true.Soler
So tell me how can it fail if you can send any message to it, just like nil... and it will still call through to any message actually understood by NSNull (including all NSObject methods). Now it can cause bugs, in that if you check expressly for equivalence to nil that will not be true. But in most coding, exactly because nil can be sent any message I just test against something like ( myString.length > 0 ) which works fine here. Indeed, this code will PREVENT possible crashes in your code when an NSNull slips in where you did not expect it, so on the balance it is a stability gain.Galsworthy
Also, where does it say is an error message? That doesn't even make any sense, why would anything wanting to send back an error message ever send NSNull in place of a NSString? In practice I have only ever seen NSNull coming out of CoreData and some JSON Parsers, in ways where this test does nothing except prevent non-obvious runtime errors. You can still check for NSNull where important.Galsworthy
The error message is mentioned in the question: -[NSNull length]: unrecognized selector sent to instance. I think it's extremely dangerous to say that your technique prevents crashes. Those crashes would only exist because the programmer didn't see that NSNull gets returned. This is a perfect example of masking bugs. And in this case, it could lead to data loss if there is some logic doing something with NSNull.Soler
why not just NSMethodSignature *sig=[self methodSignatureForSelector:aSelector];Substratum
@user: I'll have to check to make sure that would end up the same but it seems like a good idea.Galsworthy
@Georg: In the case of JSON you are masking a bug TO THE USER. That is, long after an app has shipped an NSNull can slip into a data feed and it WILL cause an exception that gets thrown. With this category instead all the user sees is a blank field somewhere. You can always include this category in release builds only if you were really worried about masking it in testing (though my advice would always be: test what is shipping).Galsworthy

© 2022 - 2024 — McMap. All rights reserved.