Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to allow NSMutableDictionary to accept 'nil' values?

Tags:

objective-c

I have this statement:

 [custData setObject: [rs stringForColumnIndex:2]  forKey: @"email"];

where [rs stringForColumnIndex:2] obtained from a SQLite3 d/b has a value of nil. The app crashes giving me the error:

NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: email)'

Is there a way to prevent this? (like a setting for NSMutableDictionary?)

UPDATE: this is what I finally did:

[custData setObject: ([rs stringForColumnIndex:2] != nil? [rs stringForColumnIndex:2]:@"") forKey: @"email"];
like image 733
SpokaneDude Avatar asked Jun 01 '12 20:06

SpokaneDude


2 Answers

There is a non-nil object called NSNull that is built specifically to represent nils in situations where "plain" nil is not acceptable. If you replace your nils with [NSNull null] object, NSDictionary will take them. You would need to check for NSNull on the way out, though.

Note that this is important only when you must differentiate between a value not being set and a value being set to nil. If your code is such that it can interpret a missing value as nil, you do not need to use NSNull at all.

like image 199
Sergey Kalinichenko Avatar answered Oct 15 '22 09:10

Sergey Kalinichenko


It is not possible with a pure NSMutableDictionary, and in most cases you want to convert nil values into [NSNull null] or just omit them from the dictionary. Sometimes (very seldom), though, it is convenient to allow nil values, and in those cases you can use CFMutableDictionary with custom callbacks.

If you go this way, I recommend that you use CoreFoundation API for all accesses, e.g. CFDictionarySetValue and CFDictionaryGetValue.

However, if you know what you're doing, you can use toll-free bridging and cast that CFMutableDictionary to NSMutableDictionary or NSDictionary. This may be useful if you have a bunch of helpers that accept NSDictionary, and you want to use them on your modified nil-capable dictionary. (Of course, make sure that the helpers aren't surprised by nil values.)

If you do the bridging, note that:

1) NSMutableDictionary setter raises errors on nil values before bridging, so you need to use CFDictionarySetValue to set values that are potentially nil.

2) technically, we're violating a contract of NSMutableDictionary here, and things may break (e.g. in future OS updates)

3) a lot of code will be very surprised to find nil values in a dictionary; you should only pass the bridged frankendictionaries to the code that you control

See ridiculousfish's post on toll-free bridging for an explanation of why a bridged CFDictionary behaves differently from NSDictionary.

Example:

#import <Foundation/Foundation.h>

const void *NullSafeRetain(CFAllocatorRef allocator, const void *value) {
    return value ? CFRetain(value) : NULL;
}
void NullSafeRelease(CFAllocatorRef allocator, const void *value) {
    if (value)
        CFRelease(value);
}
const CFDictionaryValueCallBacks kDictionaryValueCallBacksAllowingNULL = {
    .version = 0,
    .retain = NullSafeRetain,
    .release = NullSafeRelease,
    .copyDescription = CFCopyDescription,
    .equal = CFEqual,
};

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        CFMutableDictionaryRef cfdictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kDictionaryValueCallBacksAllowingNULL);
        CFDictionarySetValue(cfdictionary, @"foo", @"bar");
        CFDictionarySetValue(cfdictionary, @"boz", nil);

        NSMutableDictionary *dictionary = CFBridgingRelease(cfdictionary);
        NSLog(@"dictionary[foo] = %@", dictionary[@"foo"]);
        NSLog(@"dictionary[foo] = %@", dictionary[[@"fo" stringByAppendingString:@"o"]]);
        NSLog(@"dictionary[boz] = %@", dictionary[@"boz"]);
        NSLog(@"dictionary = %@", dictionary);
        NSLog(@"(dictionary isEqualTo: dictionary) = %d", [dictionary isEqualToDictionary:dictionary]);
    }
    return 0;
}

outputs:

dictionary[foo] = bar
dictionary[foo] = bar
dictionary[boz] = (null)
dictionary = {
    boz = (null);
    foo = bar;
}
(dictionary isEqualTo: dictionary) = 1
like image 33
Andrey Tarantsov Avatar answered Oct 15 '22 08:10

Andrey Tarantsov