How to create a NSMutableDictionary that won't crash when assigned nil? - objective-c

Is there a way to create a NSMutableDictionary category that won't crash when assigned a nil value? There is an objectForKeyedSubscript method that we can override for getting the value, but I am not finding the setter version.
I want my dictionary to not crash even when it's assigned nil through subscript.
e.g.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[#"test"] = nil; //won't crash here

I don't know what your use case is but if you are just trying to assign an empty value to a key have you looked at NSNull? If you assign a key in a dictionary to [NSNull null] it will give you most of the benefits of a nil value.

You can use 'setValue:forKey:' to set any value (including nil) in the dictionary
- (void)setValue:(id)value forKey:(NSString *)key
Adds a given key-value pair to the dictionary.
Note that when using key-value coding, the key
must be a string (see “Key-Value Coding Fundamentals”). Discussion
This method adds value and key to the dictionary using
setObject:forKey:, unless value is nil in which case the method
instead attempts to remove key using removeObjectForKey:.

You can’t do this in Objective-C. However, the code you posted does not crash.

Related

Return something other than nil from a method sent to nil

This is probably impossible with a category, but just in case it is doable, I wanted to find out.
I wrote a category on NSString and I have a category method that parses a comma delimited string into an NSArray cleaning up extra commas and spaces, and trimming the ends... so more comprehensive than the built-in method of similar functionality. Here's the rub though... what if the string is nil? Is there any way I can return an initialized NSArray with 0 objects instead of returning nil?
The scenario would go something like this...
NSArray *parsed = [someString parseStringIntoArray];
... assume someString is nil. Can I somehow get an initialized array out of this?
Obviously there are ways to work AROUND this, but keeping it clean and succinct, and using the category method... is it possible?
No. But yes, if you make some changes:
Since you call a instance method, this won't work. When you send a message to nil (aka call a method on a nil object) you will always get nil.
This is the key concept of nil itself.
You can for example add a class method and in this class method, you can then test against nil and return an empty array instead:
+ (NSArray *)parseStringIntoArray:(NSString *)string {
return [string componentsSeparatedByString:#","] ?: #[];
}
or you can simply use, what NSString has built in:
NSArray *parts = [#"foo,bar,la,le,lu" componentsSeparatedByString:#","] ?: #[];
EDIT
No, there's no way to return anything from a message sent to nil. That is baked into the very core of the runtime: objc_msgSend() is responsible for this behavior.
If the receiver is nil, objc_msgSend() resolves the expression to the appropriate 0 value for the return type of the method.
You will have to test for nil either before or after and change the value of the array manually.
(Incidentally, the fact that this is a category method is irrelevant.)

NSDictionary objectForKey is returning nil value

I have a dictionary like this
123456
{
"aaa"
"aaav"
}
But when i try to get these values in NSArray using objectForKey, I am getting nil.
The key I am using is a string.
Code :
NSArray *arr = [userdetails objectForKey:str]
Where userdetails is an NSDictionary and str is NSString which contains the key.
NSDictionary cannot contain nil objects. There are two possibilities to get a nil back from objectForKey:
Your dictionary itself has not been initialized, or
There is no object for the key that you have specified.
Please make sure that you have created an instance of your NSDictionary or a compatible class (say, NSMutableDictionary) and assign it to the variable prior to querying it for the key. Then make sure the key in question contains an object.

How do I know what a return value of zero means for NSDictionary's objectForKey method in Objective C?

The documentation for NSDictionary says that the method objectForKey returns nil if the NSDictionary does not contain your key; however, isn't nil the same as zero? If so, how do I know if the return value means that the dictionary contains the key mapped to zero or if that key is just non-existant?
NSDictionaries (and, indeed, all Cocoa collection objects) can only contain Objective-C objects, not C primitives like int. Therefore, were you to store 0 in a dictionary, you'd do it like this:
[myDictionary setObject:[NSNumber numberWithInt:0] forKey:#"myKey"];
and therefore, retrieving it would go like this:
NSNumber* resultObj = [myDictionary objectForKey:#"myKey"];
if (resultObj == nil)
{
//Key didn't exist
}
else
{
int result = [resultObj intValue];
//Now do your work.
}
This concept of 'wrapping' a C primitive in an object to store it in a collection, then 'unwrapping' it on the other end is very common in Cocoa. (You might also look into NSValue if you're trying to work with non-number values.)
Edited: Although the direction in memory NULL and nil point to happen to be 0x0 and it has always been and it may always be, you can't use that value for other purposes but to define an undefined address in memory. In the case of NULL it means an undefined pointer. nil means an undefined object pointer. The idea behind these #defines is that there's a rigid standard for programmers and programs to know and communicate undefined addresses in memory, and it could change to 0x42 in a new compiler or standard and NOTHING should happen. So, don't think nil or NULL as 0; yes they are, but that's a mere accident. It's not a feature.
nil is not the same as 0. nil is an object pointer, 0 is a numeric value

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.

How to use #encode() to get #"NSArray" in Objective-C

I'm using the runtime functions to get the type of a property (thanks to eJames for helping me to figure out this way).
The attribute string of the property looks like this:
T#"NSArray",&,Vstuff
I need to check if the property type is an array, at the moment I'm doing it like this:
- (BOOL)valueForKeyIsArray:(NSString *)key fromTagret:(id)target
{
NSString *lowerCaseKey = [self convertToKVCKey:key];
objc_property_t property = class_getProperty([target class], [lowerCaseKey UTF8String]);
NSString *propertyAttrs = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *encodedType = #"#\"NSArray\"";
NSRange range = [propertyAttrs rangeOfString:encodedType options:NSLiteralSearch];
return range.location != NSNotFound;
}
But since Apple can change the type definition string at any time, I would like to generate this #"NSArray" type string. I tried it with #encode(), but it did not work:
NSString *encodedType = [NSString stringWithUTF8String:#encode(NSArray *)];
So how can I generate this type string? Or is there a better way to check if this property attributes contain the array type?
There is no way to check this. In Objective-C source code the variables being typed as NSArray * is only there for the compiler to issue warnings. It has no meaning, and does not exist at runtime. If you mis-typed an NSArray as an NSString, you would get lots of warnings when compiling, but your code would behave exactly the same when run. At runtime all that is known is that the ivar/property is "an object".
Another way to think of it, is that once Objective-C is compiled, all object references are id references.
Just accept that if the runtime changes, your code will break, and move on. However, I think you might be miscategorizing ivars of type NSMutableArray *, CFArrayRef, or CFMutableArrayRef. You also seem to be assuming all keys correspond directly to a declared property.
The cleanest solution might be to assert that the sample object being used for the test (the target) must have a non-nil value for that key, and just grab the value and test that [[target valueForKey:key] isKindOfClass:[NSArray class]].