Why doesn't NSNull behave like nil for unimplemented methods? - objective-c

As per my understanding, NSNull exists as a replacement of nil in situations where an object is explicitly required, such in NSArray and NSDictionary collections.
One of the good parts of nil's behavior is that many verbose null checks can be skipped and it would convenient to have the same behavior implemented by NSNull. Consider the following example:
NSDictionary * json = // deserialized JSON from a REST service
NSString * name = json[#"first_name"];
if (name != nil && ![name isEqual:[NSNull null]] && [name isEqualToString:#"Gabriele"]) { ... }
the check can currently be simplified to
if (![name isEqual:[NSNull null]] && [name isEqualToString:#"Gabriele"]) { ... }
but if [NSNull null] was mimicking the behavior of nil, it could be simplified even more to
if ([name isEqualToString:#"Gabriele"]) { ... }
So my question is why didn't Apple override the -forwardInvocation: method of NSObject to mimic the behavior of nil on an unrecognized selectors?
I would like to get the philosophy behind it and to understand whether there is some drawback or some broken convention that I cannot think of.

My understanding is that nil's behavior is part of the Objective C language, not of any particular library. NSNull, on the other hand, is part of a library, with a single very specific purpose:
The NSNull class defines a singleton object used to represent null values in collection objects (which don’t allow nil values).
It is not intended for use as a "sentinel" object: if you wish to construct such an object, you can always do so, because collections themselves do not assign NSNull any particular significance. It is provided for programmers' convenience in situations when they wish to store nils, so that programmers could avoid reinventing their own null object every time they need it.
Also note that [NSNull null] is a singleton, so you can compare it with == / != operators instead of calling isEqual:
if (name != [NSNull null] && [name isEqualToString:#"Gabriele"])

Related

Inserting nil objects into an NSDictionary

If we have an API that requires only 2 out of an objects 5 properties and iPhone app doesn't require them to instantiate an object, when the object is used in the params NSDicitionary the app will crash. I was told NSDictionary will not let you assign nil values, as when it reaches nil it thinks its finished. Does objective-c have a way to spit out an objects non-nil properties into an NSDictionary?
Example:
[Drunk alloc] init];
drunk.started_drinking = [NSDate date];
drunk.stopped_drinking (we don't set this because he is still a drunk)
drunk.fat = YES;
drunk.dumb = YES;
parameters:#{
#"auth_token" :token,
#"name" : drunk.name, #"date_started" : drunk.started_drinking,
#"date_stopped" : drunk.stopped_drinking,
#"prescribing_doctor" : drunk.fat,
#"pharmacy" : drunk.dumb
}
This will crash when it gets to the stopped_drinking property. Any suggestions on how to handle this?
It's a bit long winded but you could do
static id ObjectOrNull(id object)
{
return object ?: [NSNull null];
}
parameters:#{
#"auth_token" : ObjectOrNull(token),
#"name" : ObjectOrNull(drunk.name),
#"date_started" : ObjectOrNull(drunk.started_drinking),
#"date_stopped" : ObjectOrNull(drunk.stopped_drinking),
#"prescribing_doctor" : ObjectOrNull(drunk.fat),
#"pharmacy" : ObjectOrNull(drunk.dumb),
}
You cannot insert nil into collections (dictionaries, arrays, index sets, etc).
You can, however, insert [NSNull null] into them as this is what they made it for
Inserting objects into the dictionary becomes quite easy (if the property is nil, insert an NSNull instead). Then, when pulling things out of the dictionary, a quick if(myReturnedObject == [NSNull null]) will tell you if the returned value is valid, as NSNull is a singleton and thus every NSNull is in fact the same object.
Edit: Paul.s has an excellent example of insertion behavior for your case, complete with ternary operator usage.
Edit Again: Despite the below comment, it is factually confirmed in the Apple docs linked above that NSNull does not crash when added to collections.

How to implement equality based on property values?

I’m implementing -isEqual: for my custom class. The equality is based on the property values, ie. if all properties are equal, the objects are considered equal. Together with the traditional class check the code looks like this:
- (BOOL) isEqual: (id) object
{
return [object class] == [self class]
&& [[object someProperty] isEqual:someProperty]
&& [[object otherProperty] isEqual:otherProperty];
}
But this fails for nil property values, ie. two objects of the class having nil values stored in someProperty are considered non-equal, whereas I would like them to be equal. Thus I arrived at the following version:
- (BOOL) isEqual: (id) object
{
#define equals(a, b) ((a == b) || ([a isEqual:b]))
return equals([object class], [self class])
&& equals([object someProperty], someProperty)
&& equals([object otherProperty], otherProperty);
}
This seems to work fine. Is this the “standard” way to solve the equality? Seems overly complex to me.
isEqual: is very much object-specific. It's a semantic equality. It is up to you to define for every class what isEqual means. Thus there is no standard way to do it.
The simplest implementation is return self == object, your second implementation is very generic and nice, but not necessarily well suited to every class. Per example, for a Person, comparing the emails could be sufficient, or the emails and first names if you suppose an email could be used by several family members.

if statement fails with variable from CoreData managed object

I'm having some trouble with Managed Objects... imagine that.
Here is one real doozy, maybe someone can help.
So I have a managed object class called "Format" which is an entity. Anyway, it has a property called "slot" that's an NSNumber. Now the number can have values from zero to four, but if it does not have a value then I want the NSNumber object to be equal to "nil" as opposed to zero. I wasn't having any luck with that since evidently being zero is the same as being "nil." Ugh. (Is there a way to tell if the pointer is simply empty, "nil," as opposed to pointing to a zero value?)
At any rate, I tried a work-around which was to render the variable into text like so:
if(#"myFormat.slot" != #"(null)")
NSLog(#"slot==%#",myFormat.slot);
But the problem is that I got this in my log:
slot==(null)
So, OK... what the heck? If #"myFormat.slot" == #"(null)" then how the heck is that if statement resolving...?!?!
I'm truly baffled now... please someone help me out.
You won't ever get a nil back from an attribute. Instead, you get a [NSNull null] object.
In Objective-C, nil and null are not interchangeable. When you see nil you are almost looking at a dereferenced pointer. nil is intended to convey that no object has been assigned to the symbol. null by contrast is the singleton instance of [NSNull null]. It is used as a placeholder to indicate that some value, represented by an object, has not been set. In other words, a value of nil doesn't make sense in Objective-C.
In Core Data, relationships and attributes are not treated the same even though they both return objects. A relationship is a pointer to an external object and therefore can have a return nil if no object has been set on the other side of the relationship. An attribute is a value only held by an object and therefore is always represented by [NSNull null] if not set.
By default, all attributes with numerical value will return an NSNumber object initialized to zero. If you remove the default you get [NSNull null].
However, since [NSNull null] is a singleton you can use a simple pointer comparison to check for it e.g.
if (myMo.numericalAttribute == [NSNull null])...
However, that is considered bad practice.
This if(#"myFormat.slot" != #"(null)") is always true, because #"myString" creates an autoreleased string. Therefore you are checking the addresses of to different autorelease strings and (surprise) they are different.
Have you tried something like this:
if ([myNumber isEqualTo: [NSNumber numberWithInt: 0]]) self.myNumber = nil;
When comparing the contents of 2 NSString objects, use the isEqual: method or, if you need to perform a Unicode-based comparison of strings, use isEqualToString:.
If you want to set myFormat.slot to nil, do this:
myFormat.slot = nil;
To set the value of the slot attribute to zero:
myFormat.slot = [NSNumber numberWithInt:0];
To compare values wrapped in NSNumbers (e.g. check if your slot is equal to the 0 integer value), do this:
if ([myFormat.slot intValue] == 0) { // primitive type comparison
//...
}
or this:
if ([myFormat.slot isEqual:[NSNumber numberWithInt:0]]) { // object comparison
//...
}
but NOT THIS:
if (myFormat.slot == 0) { // pointer comparison!!
//...
}
To check if slot is empty (nil):
if (myFormat.slot == nil) {
//...
}
Keep in mind: Core Data standard attributes are always mapped to non-primitive values (NSString, NSDate, NSNumber). Always use isEqual:, compare: & friends when comparing objects' values. The == operator performs pointer comparison when you use it with non-primitives.

TouchJSON, dealing with NSNull

Hi
I am using TouchJSON to deserialize some JSON. I have been using it in the past and on those occasions I dealt with occurrences of NSNull manually. I would think the author had to deal with this as well, so me doing that again would just be overhead. I then found this in the documentation:
Avoiding NSNull values in output.
NSData *theJSONData = /* some JSON data */
CJSONDeserializer *theDeserializer = [CJSONDeserializer deserializer];
theDeserializer.nullObject = NULL;
NSError *theError = nil;
id theObject = [theDeserializer deserialize:theJSONData error:&theError];}
The way I understand it the user of the class can pass a C-style null pointer to the deserializer and when it encounters a NSNull it will insert the values (NULL) passed to it. So later on when I use the values I won't get NSNull, but NULL.
This seems strange, the return value is an NSDictionary which can only contain Objects, shouldn't the value default to 'nil' instead?
If it is NULL can I check the values like this?
if([jsonDataDict objectForKey:someKey] == NULL)
It would seem more logically to be able to do this:
if(![jsonDataDict objectForKey:someKey])
No to mention all the cases where passing nil is allowed but passing NULL causes a crash.
Or can I just pass 'nil' to the deserializer?
Much of this stems from me still struggling with nil, NULL, [NSNULL null], maybe I am failing to see the potential caveats in using nil.
For another JSON library, but with the same issues, I've created the following category on NSDictionary:
#implementation NSDictionary (Utility)
// in case of [NSNull null] values a nil is returned ...
- (id)objectForKeyNotNull:(id)key {
id object = [self objectForKey:key];
if (object == [NSNull null])
return nil;
return object;
}
#end
Whenever I deal with JSON data from said library, I retrieve values like this:
NSString *someString = [jsonDictionary objectForKeyNotNull:#"SomeString"];
This way the code in my projects become a lot cleaner and at the same time I don't have to think about dealing with [NSNull null] values and the like.
nil and NULL are actually both equal to zero, so they are, in practice, interchangeable. But you're right, for consistency, the documentation for TouchJSON should have used theDeserializer.nullObject = nil instead of NULL.
Now, when you do that, your second predicate actually works fine:
if (![jsonDataDict objectForKey:someKey])
because TouchJSON omits the key from the dictionary when you have nullObject set to nil (or NULL). When the key doesn't exist in the dictionary, NSDictionary returns nil, which is zero so your if condition works as you expect.
If you don't specify nullObject as nil, you can instead check for null like so:
if ([jsonDataDict objectForKey:someKey] == [NSNull null])
There are libraries which deal with it. One of them is SwiftyJSON in Swift, another one is NSTEasyJSON in Objective-C.
With this library (NSTEasyJSON) it will be easy to deal with such problems. In your case you can just check values you need:
NSTEasyJSON *JSON = [NSTEasyJSON withData:JSONData];
NSString *someValue = JSON[someKey].string;
This value will be NSString or nil and you should not check it for NSNull, NULL yourself.

Are NULL and nil equivalent?

Actually my question here is: are null and nil equivalent or not?
I have an example but I am confused when they are equal when they are not.
NSNull *nullValue = [NSNull null];
NSArray *arrayWithNull = [NSArray arrayWithObject:nullValue];
NSLog(#"arrayWithNull: %#", arrayWithNull);
id aValue = [arrayWithNull objectAtIndex:0];
if (aValue == nil) {
NSLog(#"equals nil");
} else if (aValue == [NSNull null]) {
NSLog(#"equals NSNull instance");
if ([aValue isEqual:nil]) {
NSLog(#"isEqual:nil");
}
}
Here in the above case it shows that both null and nil are not equal and it displays "equals NSNull instance"
NSString *str=NULL;
id str1=nil;
if(str1 == str)
{
printf("\n IS EQUAL........");
}
else
{
printf("\n NOT EQUAL........");
}
And in the second case it shows both are equal and it displays "IS EQUAL".
Anyone's help will be much appreciated.
Thank you,
Monish.
nil and NULL are essentially the same, nil is something like (NSObject *)0, while NULL is more like (void *)0. But both are pointers with an integer value of zero. You can send messages to nil without raising an error.
NSNull and NULL (or nil, of course) are different things, however. You just use NSNull as a helper to add an empty object to an NSArray or another container class, since you can't add nil to them. So instead, you use [NSNull null] as a replacement, and you have to check if an array element is NSNull, not if it's nil (it will never be equal to nil).
From http://www.iphonedevsdk.com/forum/iphone-sdk-development/34826-nil-vs-null.html
nil and NULL are 100% interchangeable.
From:
NULL is for C-style memory pointers.
nil is for Objective-C objects.
Nil is for Objective-C classes.
Whenever you're writing Objective-C code, use nil
Whenever you're writing C code, use NULL
But ultimately they're all defined as the same thing -- (void *)0, I think -- so in practice it doesn't really matter.
The concept is the same, with the difference that it's valid to send messages (call method) to nil.
NSNull is a real (singleton) class, that can be used for arrays or dictionnaries, who don't accept NULL or nil values.
Biggest difference between them: sending a message to an NSNULL object is probably going to cause a crash, whereas it's cool to send any message to nil. For example, if you use a key path to get an array, like so:
NSArray *departmentNames = [departments valueForKey:#"name"];
Then you will have an NSNULL object for any department whose name is nil. So, this is going to cause a crash:
for (NSString *name in departmentNames)
NSLog(#"%#", [name lowercaseString]);
whenever name is NSNull, because you just sent an unknown selector (lowercaseString) to an NSNull.
Lesson: check for the NSNull object in an array before sending any message to its elements.
for (NSString *name in departmentNames)
if (name != [NSNull null])
NSLog(#"%#", [name lowercaseString]);
No, NSNull and nil are not the same. They both represent a lack of value, and you might want to treat them the same, but they are still not equal.
The NSNull object instance represents a null value, for example when you read data from a database that has null values.
The nil value is a null pointer, i.e. it doesn't point to any object instance.
In your second code you don't have any NSNull instance. An NSString pointer that contains a null pointer is not an NSNull instance, it's still just a null pointer. You are comparing one null pointer to another, and they are of course equal.
Make sure you typecast [NSNull null] to object type that you are comparing
NSArray list;
if(list==(NSArray *)[NSNull null])
// do something
otherwise you will receive a warning message saying "Comparison of distinct pointer types('type *' and 'NSNull *')